O contexto muda muito mais devagar em novos kernels Linux

99

Estamos procurando atualizar o sistema operacional em nossos servidores do Ubuntu 10.04 LTS para o Ubuntu 12.04 LTS. Infelizmente, parece que a latência para executar um thread que se tornou executável aumentou significativamente do kernel 2.6 para o kernel 3.2. Na verdade, os números de latência que estamos obtendo são difíceis de acreditar.

Deixe-me ser mais específico sobre o teste. Temos um programa que executa dois threads. O primeiro thread obtém a hora atual (em tiques usando RDTSC) e então sinaliza uma variável de condição uma vez por segundo. O segundo encadeamento espera a variável de condição e desperta quando é sinalizado. Em seguida, obtém a hora atual (em tiques usando RDTSC). A diferença entre a hora no segundo encadeamento e a hora no primeiro encadeamento é calculada e exibida no console. Depois disso, o segundo encadeamento espera a variável de condição mais uma vez. Ele será sinalizado novamente pelo primeiro thread após cerca de um segundo.

Então, em poucas palavras, obtemos um thread para thread comunicação via medição de latência de variável de condição uma vez por segundo como resultado.

No kernel 2.6.32, essa latência está em algum lugar na ordem de 2,8-3,5 us, o que é razoável. No kernel 3.2.0, essa latência aumentou para algo em torno de 40-100 us. Excluí quaisquer diferenças de hardware entre os dois hosts. Eles funcionam em hardware idêntico (processadores dual socket X5687 {Westmere-EP} rodando a 3,6 GHz com hyperthreading, speedstep e todos os estados C desligados). O aplicativo de teste altera a afinidade dos threads para executá-los em núcleos físicos independentes do mesmo soquete (ou seja, o primeiro thread é executado no Core 0 e o segundo thread é executado no Core 1), portanto, não há retorno de threads no núcleos ou salto / comunicação entre sockets.

A única diferença entre os dois hosts é que um está executando o Ubuntu 10.04 LTS com kernel 2.6.32-28 (a caixa de troca rápida de contexto) e o outro está executando o Ubuntu 12.04 LTS mais recente com kernel 3.2.0-23 (o contexto lento caixa de comutação). Todas as configurações e hardware do BIOS são idênticos.

Houve alguma mudança no kernel que poderia ser responsável por essa ridícula lentidão em quanto tempo leva para um thread ser agendado para ser executado?

Update: Se você gostaria de executar o teste em seu host e Linux build, eu postei o código para pastebin para sua leitura. Ajuntar com:

g++ -O3 -o test_latency test_latency.cpp -lpthread

Execute com (assumindo que você tenha pelo menos uma caixa dual-core):

./test_latency 0 1 # Thread 1 on Core 0 and Thread 2 on Core 1

Atualização 2 : Depois de muito pesquisar os parâmetros do kernel, postar sobre mudanças no kernel e pesquisa pessoal, descobri qual é o problema e postei a solução como resposta a essa pergunta.

Michael Goldshteyn
fonte
1
apenas um palpite, mas talvez a alteração de um parâmetro de /proc/sys/kernel/*pode funcionar? Se você encontrar algo que funcione, coloque essa configuração /etc/sysctl.confou um arquivo /etc/sysctl.d/para fazê-lo persistir nas reinicializações.
Carlos Campderrós
1
Eu comparei / proc / sys / kernel entre os dois hosts, mas não vejo diferenças significativas, especialmente em quaisquer itens de configuração relacionados ao agendamento.
Michael Goldshteyn
Lembro-me vagamente de um boato de que RDTSC não é necessariamente sincronizado corretamente entre os núcleos, mas eu esperaria que, se isso fosse um problema, você veria reversão de tempo. Você tentou mexer nas afinidades para executar os dois threads no mesmo núcleo e ver o que acontece?
David dado em
Em núcleos da Intel como este novo, o RDTSC funciona perfeitamente em todos os núcleos, especialmente núcleos no mesmo CPU (ou seja, no mesmo soquete). Curiosamente, se ambos os threads são executados no mesmo núcleo, as latências caem para 4-10 nós no kernel mais recente e aprox. 3 us no kernel antigo.
Michael Goldshteyn
Apenas um comentário geral - depender de TSCs para serem sincronizados é duvidoso na melhor das hipóteses, embora, em seu caso específico, como você está usando dois núcleos em um chip físico, isso realmente deva funcionar.
twalberg

Respostas:

95

A solução para o problema de desempenho de ativação de thread ruim em kernels recentes tem a ver com a mudança para o intel_idledriver cpuidle de acpi_idle, o driver usado em kernels mais antigos. Infelizmente, o intel_idledriver ignora a configuração do BIOS do usuário para os estados C e dança em sua própria melodia . Em outras palavras, mesmo se você desabilitar completamente todos os estados C no BIOS do seu PC (ou servidor), este driver ainda os forçará durante períodos de breve inatividade, que quase sempre acontecem, a menos que um benchmark sintético que consuma todo o núcleo (por exemplo, estresse ) está correndo. Você pode monitorar as transições de estado C, junto com outras informações úteis relacionadas às frequências do processador, usando a maravilhosa ferramenta i7z do Google na maioria dos hardwares compatíveis.

Para ver qual driver cpuidle está atualmente ativo em sua configuração, basta catar o current_driverarquivo na cpuidleseção /sys/devices/system/cpuseguinte:

cat /sys/devices/system/cpu/cpuidle/current_driver

Se você deseja que seu moderno sistema operacional Linux tenha a menor latência de alternância de contexto possível, adicione os seguintes parâmetros de inicialização do kernel para desativar todos esses recursos de economia de energia:

No Ubuntu 12.04, você pode fazer isso adicionando-os à GRUB_CMDLINE_LINUX_DEFAULTentrada em /etc/default/grube executando update-grub. Os parâmetros de inicialização a serem adicionados são:

intel_idle.max_cstate=0 processor.max_cstate=0 idle=poll

Aqui estão os detalhes sangrentos sobre o que as três opções de inicialização fazem:

Definir intel_idle.max_cstatecomo zero irá reverter seu driver cpuidle para acpi_idle(pelo menos pela documentação da opção) ou desativá-lo completamente. Na minha caixa, ele está completamente desativado (ou seja, exibir o current_driverarquivo em /sys/devices/system/cpu/cpuidleproduz uma saída de none). Neste caso, a segunda opção de inicialização processor.max_cstate=0é desnecessária. No entanto, a documentação afirma que definir max_cstate como zero para o intel_idledriver deve reverter o sistema operacional para o acpi_idledriver. Portanto, coloquei a segunda opção de inicialização para o caso.

A processor.max_cstateopção define o estado C máximo do acpi_idledriver para zero, desabilitando-o também. Eu não tenho um sistema para testar isso, porque intel_idle.max_cstate=0desativa completamente o driver cpuidle em todo o hardware disponível para mim. No entanto, se sua instalação revertê-lo de intel_idlepara acpi_idlecom apenas a primeira opção de inicialização, informe-me se a segunda opção processor.max_cstatefez o que foi documentado nos comentários para que eu possa atualizar esta resposta.

Finalmente, o último dos três parâmetros, idle=pollé um verdadeiro devorador de poder. Isso desabilitará C1 / C1E, o que removerá o bit final restante de latência à custa de muito mais consumo de energia, então use esse apenas quando for realmente necessário. Para a maioria, isso será um exagero, já que a latência C1 * não é tão grande. Usando meu aplicativo de teste em execução no hardware que descrevi na pergunta original, a latência foi de 9 para 3 nós. Esta é certamente uma redução significativa para aplicativos altamente sensíveis à latência (por exemplo, negociação financeira, telemetria / rastreamento de alta precisão, aquisição de dados de alta frequência, etc ...), mas pode não valer a pena o impacto de energia elétrica incorrido para a grande maioria dos aplicativos de desktop. A única maneira de saber com certeza é traçar um perfil de melhoria de desempenho de seu aplicativo vs.

Atualizar:

Após testes adicionais com vários idle=*parâmetros, descobri que configurar idlepara mwaitse compatível com o seu hardware é uma ideia muito melhor. Parece que o uso das MWAIT/MONITORinstruções permite que a CPU entre em C1E sem que nenhuma latência perceptível seja adicionada ao tempo de ativação do thread. Com o idle=mwait, você obterá temperaturas de CPU mais frias (em comparação com idle=poll), menos uso de energia e ainda manterá as excelentes baixas latências de um loop ocioso de polling. Portanto, meu conjunto recomendado atualizado de parâmetros de inicialização para latência de ativação de thread de CPU baixa com base nestas descobertas é:

intel_idle.max_cstate=0 processor.max_cstate=0 idle=mwait

O uso de em idle=mwaitvez de idle=polltambém pode ajudar com o início do Turbo Boost (ajudando a CPU a ficar abaixo de seu TDP [Energia de Design Térmico]) e hyperthreading (para o qual MWAIT é o mecanismo ideal para não consumir um núcleo físico inteiro ao mesmo tempo tempo evitando os estados C superiores). No entanto, isso ainda precisa ser provado em testes, que continuarei a fazer.

Atualização 2:

A mwaitopção idle foi removida dos kernels 3.x mais novos (graças ao usuário ck_ pela atualização). Isso nos deixa com duas opções:

idle=halt- Deve funcionar bem mwait, mas teste para ter certeza de que é o caso do seu hardware. A HLTinstrução é quase equivalente a uma MWAITdica de estado com 0. O problema reside no fato de que uma interrupção é necessária para sair de um estado HLT, enquanto uma gravação de memória (ou interrupção) pode ser usada para sair do estado MWAIT. Dependendo do que o kernel do Linux usa em seu loop ocioso, isso pode tornar o MWAIT potencialmente mais eficiente. Então, como falei teste / perfil e veja se atende às suas necessidades de latência ...

e

idle=poll - A opção de melhor desempenho, em detrimento de energia e calor.

Michael Goldshteyn
fonte
Desculpe, mas por que você esperava que os estados C fossem gerenciados pelo firmware? Os estados suspensos são estados de tempo de execução e são gerenciados pelo sistema operacional por design. Como você descobriu, se não quiser suspender o tempo de execução, não o use.
Andy Ross
6
Desculpe, mas os estados C, EIST e C1E podem ser desligados na BIOS. Espero que o sistema operacional respeite minhas configurações de BIOS. Isso é especialmente verdadeiro, dadas as ferramentas e documentação horríveis neste caso.
Michael Goldshteyn
4
Desligado por meio de seu BIOS, talvez. Não sei de nada em uma especificação relevante que exija isso. Desculpe, mas "esperar" qualquer coisa da BIOS vai incomodar você repetidamente. A melhor coisa que o firmware pode fazer em um PC moderno é nada. Lamento que você tenha ficado surpreso, mas, francamente, este é um erro do usuário. Seu benchmark mediu os tempos de suspensão e retomada.
Andy Ross
19
Uma das funções da seleção de recursos do BIOS é habilitar / desabilitar dispositivos. Em alguns casos, essas seleções são forçadas no sistema operacional (por exemplo, USB na placa-mãe, eSATA e NICs). Em outros, espera-se que o sistema operacional respeite seus desejos (por exemplo, EIST, estados C, Hyperthreading, Execute Disable, AES-NI, Virtualization, etc ...). O BIOS fornece uma única superfície central de seleção de dispositivo / recurso que é neutra do sistema operacional. Isso permite que o usuário instale vários sistemas operacionais (talvez muito diferentes) no host, todos usando os mesmos recursos de hardware. No entanto, esta resposta é subjetiva, portanto, terá que concordar para discordar.
Michael Goldshteyn
1
idle = mwait não é mais compatível com o kernel 3.x recente lkml.org/lkml/2013/2/10/21 algum conselho alternativo?
ck_
8

Talvez o que tenha ficado mais lento seja o futex, o bloco de construção das variáveis ​​de condição. Isso vai lançar alguma luz:

strace -r ./test_latency 0 1 &> test_latency_strace & sleep 8 && killall test_latency

então

for i in futex nanosleep rt_sig;do echo $i;grep $i test_latency_strace | sort -rn;done

que mostrará os microssegundos necessários para as chamadas de sistema interessantes, classificadas por tempo.

No kernel 2.6.32

$ for i in futex nanosleep rt_sig;do echo $i;grep $i test_latency_strace | sort -rn;done
futex
 1.000140 futex(0x601ac4, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601ac0, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1
 1.000129 futex(0x601ac4, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601ac0, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1
 1.000124 futex(0x601ac4, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601ac0, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1
 1.000119 futex(0x601ac4, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601ac0, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1
 1.000106 futex(0x601ac4, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601ac0, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1
 1.000103 futex(0x601ac4, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601ac0, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1
 1.000102 futex(0x601ac4, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601ac0, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1
 0.000125 futex(0x7f98ce4c0b88, FUTEX_WAKE_PRIVATE, 2147483647) = 0
 0.000042 futex(0x601b00, FUTEX_WAKE_PRIVATE, 1) = 1
 0.000038 futex(0x601b00, FUTEX_WAKE_PRIVATE, 1) = 1
 0.000037 futex(0x601b00, FUTEX_WAKE_PRIVATE, 1) = 1
 0.000030 futex(0x601b00, FUTEX_WAKE_PRIVATE, 1) = 1
 0.000029 futex(0x601b00, FUTEX_WAKE_PRIVATE, 1) = 0
 0.000028 futex(0x601b00, FUTEX_WAKE_PRIVATE, 1) = 1
 0.000027 futex(0x601b00, FUTEX_WAKE_PRIVATE, 1) = 1
 0.000018 futex(0x7fff82f0ec3c, FUTEX_WAKE_PRIVATE, 1) = 0
nanosleep
 0.000027 nanosleep({1, 0}, {1, 0}) = 0
 0.000019 nanosleep({1, 0}, {1, 0}) = 0
 0.000019 nanosleep({1, 0}, {1, 0}) = 0
 0.000018 nanosleep({1, 0}, {1, 0}) = 0
 0.000018 nanosleep({1, 0}, {1, 0}) = 0
 0.000018 nanosleep({1, 0}, {1, 0}) = 0
 0.000018 nanosleep({1, 0}, 0x7fff82f0eb40) = ? ERESTART_RESTARTBLOCK (To be restarted)
 0.000017 nanosleep({1, 0}, {1, 0}) = 0
rt_sig
 0.000045 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
 0.000040 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
 0.000038 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
 0.000035 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
 0.000034 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
 0.000033 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
 0.000032 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
 0.000032 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
 0.000031 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
 0.000031 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
 0.000028 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
 0.000028 rt_sigaction(SIGRT_1, {0x37f8c052b0, [], SA_RESTORER|SA_RESTART|SA_SIGINFO, 0x37f8c0e4c0}, NULL, 8) = 0
 0.000027 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
 0.000027 rt_sigaction(SIGRTMIN, {0x37f8c05370, [], SA_RESTORER|SA_SIGINFO, 0x37f8c0e4c0}, NULL, 8) = 0
 0.000027 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
 0.000025 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
 0.000025 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
 0.000023 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
 0.000023 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
 0.000022 rt_sigprocmask(SIG_UNBLOCK, [RTMIN RT_1], NULL, 8) = 0
 0.000022 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
 0.000021 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
 0.000021 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
 0.000021 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
 0.000021 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
 0.000021 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
 0.000019 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0

No kernel 3.1.9

$ for i in futex nanosleep rt_sig;do echo $i;grep $i test_latency_strace | sort -rn;done
futex
 1.000129 futex(0x601764, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601760, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1
 1.000126 futex(0x601764, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601760, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1
 1.000122 futex(0x601764, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601760, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1
 1.000115 futex(0x601764, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601760, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1
 1.000114 futex(0x601764, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601760, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1
 1.000112 futex(0x601764, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601760, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1
 1.000109 futex(0x601764, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601760, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1
 0.000139 futex(0x3f8b8f2fb0, FUTEX_WAKE_PRIVATE, 2147483647) = 0
 0.000043 futex(0x601720, FUTEX_WAKE_PRIVATE, 1) = 1
 0.000041 futex(0x601720, FUTEX_WAKE_PRIVATE, 1) = 1
 0.000037 futex(0x601720, FUTEX_WAKE_PRIVATE, 1) = 1
 0.000036 futex(0x601720, FUTEX_WAKE_PRIVATE, 1) = 1
 0.000034 futex(0x601720, FUTEX_WAKE_PRIVATE, 1) = 1
 0.000034 futex(0x601720, FUTEX_WAKE_PRIVATE, 1) = 1
nanosleep
 0.000025 nanosleep({1, 0}, 0x7fff70091d00) = 0
 0.000022 nanosleep({1, 0}, {0, 3925413}) = ? ERESTART_RESTARTBLOCK (Interrupted by signal)
 0.000021 nanosleep({1, 0}, 0x7fff70091d00) = 0
 0.000017 nanosleep({1, 0}, 0x7fff70091d00) = 0
 0.000017 nanosleep({1, 0}, 0x7fff70091d00) = 0
 0.000017 nanosleep({1, 0}, 0x7fff70091d00) = 0
 0.000017 nanosleep({1, 0}, 0x7fff70091d00) = 0
 0.000017 nanosleep({1, 0}, 0x7fff70091d00) = 0
rt_sig
 0.000045 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
 0.000044 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
 0.000043 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
 0.000040 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
 0.000038 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
 0.000037 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
 0.000036 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
 0.000036 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
 0.000035 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
 0.000035 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
 0.000035 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
 0.000035 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
 0.000034 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
 0.000031 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
 0.000027 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
 0.000027 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
 0.000027 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
 0.000027 rt_sigaction(SIGRT_1, {0x3f892067b0, [], SA_RESTORER|SA_RESTART|SA_SIGINFO, 0x3f8920f500}, NULL, 8) = 0
 0.000026 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
 0.000026 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
 0.000025 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
 0.000024 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
 0.000023 rt_sigprocmask(SIG_UNBLOCK, [RTMIN RT_1], NULL, 8) = 0
 0.000023 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
 0.000022 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
 0.000021 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
 0.000019 rt_sigaction(SIGRTMIN, {0x3f89206720, [], SA_RESTORER|SA_SIGINFO, 0x3f8920f500}, NULL, 8) = 0

Encontrei este relatório de bug de 5 anos que contém um teste de desempenho de "pingue-pongue" que compara

  1. mutex libpthread de thread único
  2. variável de condição libpthread
  3. sinais simples do Unix

Eu tive que adicionar

#include <stdint.h>

a fim de compilar, o que fiz com este comando

g++ -O3 -o condvar-perf condvar-perf.cpp -lpthread -lrt

No kernel 2.6.32

$ ./condvar-perf 1000000
NPTL
mutex                 elapsed:    29085 us; per iteration:   29 ns / 9.4e-05 context switches.
c.v. ping-pong test   elapsed:  4771993 us; per iteration: 4771 ns / 4.03 context switches.
signal ping-pong test elapsed:  8685423 us; per iteration: 8685 ns / 4.05 context switches.

No kernel 3.1.9

$ ./condvar-perf 1000000
NPTL
mutex                 elapsed:    26811 us; per iteration:   26 ns / 8e-06 context switches.
c.v. ping-pong test   elapsed: 10930794 us; per iteration: 10930 ns / 4.01 context switches.
signal ping-pong test elapsed: 10949670 us; per iteration: 10949 ns / 4.01 context switches.

Concluo que, entre o kernel 2.6.32 e o 3.1.9, a troca de contexto realmente ficou mais lenta, embora não tanto quanto você observa no kernel 3.2. Sei que isso ainda não respondeu à sua pergunta, vou continuar pesquisando.

Edit: descobri que alterar a prioridade em tempo real do processo (ambos os threads) melhora o desempenho em 3.1.9 para corresponder a 2.6.32. No entanto, definir a mesma prioridade em 2.6.32 torna mais lento ... vai entender - vou dar uma olhada nisso.

Aqui estão meus resultados agora:

No kernel 2.6.32

$ ./condvar-perf 1000000
NPTL
mutex                 elapsed:    29629 us; per iteration:   29 ns / 0.000418 context switches.
c.v. ping-pong test   elapsed:  6225637 us; per iteration: 6225 ns / 4.1 context switches.
signal ping-pong test elapsed:  5602248 us; per iteration: 5602 ns / 4.09 context switches.
$ chrt -f 1 ./condvar-perf 1000000
NPTL
mutex                 elapsed:    29049 us; per iteration:   29 ns / 0.000407 context switches.
c.v. ping-pong test   elapsed: 16131360 us; per iteration: 16131 ns / 4.29 context switches.
signal ping-pong test elapsed: 11817819 us; per iteration: 11817 ns / 4.16 context switches.
$ 

No kernel 3.1.9

$ ./condvar-perf 1000000
NPTL
mutex                 elapsed:    26830 us; per iteration:   26 ns / 5.7e-05 context switches.
c.v. ping-pong test   elapsed: 12812788 us; per iteration: 12812 ns / 4.01 context switches.
signal ping-pong test elapsed: 13126865 us; per iteration: 13126 ns / 4.01 context switches.
$ chrt -f 1 ./condvar-perf 1000000
NPTL
mutex                 elapsed:    27025 us; per iteration:   27 ns / 3.7e-05 context switches.
c.v. ping-pong test   elapsed:  5099885 us; per iteration: 5099 ns / 4 context switches.
signal ping-pong test elapsed:  5508227 us; per iteration: 5508 ns / 4 context switches.
$ 
amdn
fonte
Executei no Fedora e CentOS, não tenho Ubuntu. Vou postar meus resultados.
amdn
OK, eu executei em ambos os hosts (ou seja, e kernels diferentes) e os resultados mostram quase nenhuma disparidade. Portanto, este teste não destacou nenhuma diferença. O tempo de chamada do futex difere na quarta casa decimal - uma redução insignificante no desempenho. Err, espere, os números inteiros estão em segundos? Acabei de ver que você postou seus resultados e eles parecem semelhantes aos meus ...
Michael Goldshteyn
Ok, isso exclui a implementação de futex - estamos de volta à sua teoria de troca de contexto ... sinta-se à vontade para excluir esta resposta, pois ela realmente pertence aos comentários ... Eu só queria a capacidade de formatar os comandos.
amdn
Sim, os tempos estão em segundos ... as chamadas para futex que duram mais de um segundo são para o encadeamento aguardando a condição.
amdn
Então, o que você extrai dos resultados?
Michael Goldshteyn
1

Você também pode ver processadores clicando para baixo em processos mais recentes e kernels Linux devido ao driver pstate que é separado dos c-states. Além disso, para desativar isso, você deve usar o seguinte parâmetro de kernel:

intel_pstate=disable

Kyle Brandt
fonte