Los cambios de contexto son mucho más lentos en los nuevos kernels de Linux

99

Estamos buscando actualizar el sistema operativo en nuestros servidores de Ubuntu 10.04 LTS a Ubuntu 12.04 LTS. Desafortunadamente, parece que la latencia para ejecutar un hilo que se ha vuelto ejecutable ha aumentado significativamente del kernel 2.6 al kernel 3.2. De hecho, los números de latencia que estamos obteniendo son difíciles de creer.

Déjame ser más específico sobre la prueba. Tenemos un programa que ejecuta dos subprocesos. El primer hilo obtiene la hora actual (en tics usando RDTSC) y luego señala una variable de condición una vez por segundo. El segundo subproceso espera la variable de condición y se despierta cuando se le indica. Luego obtiene la hora actual (en tics usando RDTSC). La diferencia entre el tiempo del segundo hilo y el tiempo del primer hilo se calcula y se muestra en la consola. Después de esto, el segundo subproceso espera una vez más la variable de condición. El primer hilo volverá a señalarlo después de aproximadamente un segundo paso.

Entonces, en pocas palabras, obtenemos una comunicación de hilo a hilo a través de la medición de latencia de la variable de condición una vez por segundo como resultado.

En el kernel 2.6.32, esta latencia está en algún lugar del orden de 2.8-3.5 us, lo cual es razonable. En el kernel 3.2.0, esta latencia se ha incrementado en algún lugar del orden de 40-100 us. He excluido cualquier diferencia de hardware entre los dos hosts. Se ejecutan en hardware idéntico (procesadores X5687 {Westmere-EP} de doble zócalo que funcionan a 3.6 GHz con hyperthreading, speedtep y todos los estados C desactivados). La aplicación de prueba cambia la afinidad de los subprocesos para ejecutarlos en núcleos físicos independientes del mismo socket (es decir, el primer subproceso se ejecuta en Core 0 y el segundo subproceso se ejecuta en Core 1), por lo que no hay rebotes de subprocesos en núcleos o rebote / comunicación entre sockets.

La única diferencia entre los dos hosts es que uno ejecuta Ubuntu 10.04 LTS con kernel 2.6.32-28 (el cuadro de cambio de contexto rápido) y el otro ejecuta el último Ubuntu 12.04 LTS con kernel 3.2.0-23 (el contexto lento caja de interruptores). Todas las configuraciones y el hardware del BIOS son idénticos.

¿Ha habido algún cambio en el kernel que pueda explicar esta ridícula ralentización en el tiempo que tarda en programarse la ejecución de un hilo?

Actualización: si desea ejecutar la prueba en su host y compilación de Linux, he publicado el código en pastebin para su lectura. Compilar con:

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

Ejecute con (suponiendo que tenga al menos una caja de doble núcleo):

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

Actualización 2 : Después de buscar mucho en los parámetros del kernel, publicaciones sobre cambios en el kernel e investigación personal, he descubierto cuál es el problema y he publicado la solución como respuesta a esta pregunta.

Michael Goldshteyn
fuente
1
solo una suposición, pero tal vez cambiar un parámetro de /proc/sys/kernel/*puede funcionar. Si encuentra algo que funcione, coloque esa configuración /etc/sysctl.confo un archivo /etc/sysctl.d/para que persista entre reinicios.
Carlos Campderrós
1
Comparé / proc / sys / kernel entre los dos hosts, pero no veo diferencias significativas, especialmente en los elementos de configuración relacionados con la programación.
Michael Goldshteyn
Recuerdo vagamente un rumor de que RDTSC no está necesariamente sincronizado correctamente entre los núcleos, pero esperaría que si esto fuera un problema, vería una inversión de tiempo. ¿Ha intentado manipular las afinidades para ejecutar ambos hilos en el mismo núcleo y ver qué sucede?
David dado el
En los núcleos Intel, este nuevo RDTSC funciona perfectamente en todos los núcleos, especialmente en los núcleos de la misma CPU (es decir, el mismo socket). Curiosamente, si ambos subprocesos se ejecutan en el mismo núcleo, las latencias bajan a 4-10 us en el kernel más nuevo y aprox. 3 us en el kernel más antiguo.
Michael Goldshteyn
Solo un comentario general: confiar en que los TSC estén sincronizados es dudoso en el mejor de los casos, aunque en su caso específico, dado que está usando dos núcleos en un chip físico, en realidad debería funcionar.
twalberg

Respuestas:

95

La solución al problema de rendimiento deintel_idle activación de subprocesos incorrectos en los núcleos recientes tiene que ver con el cambio al controlador cpuidle de acpi_idle, el controlador utilizado en los núcleos más antiguos. Lamentablemente, el intel_idlecontrolador ignora la configuración de BIOS del usuario para los estados C y baila a su propio ritmo . En otras palabras, incluso si deshabilita por completo todos los estados C en el BIOS de su PC (o servidor), este controlador los activará durante períodos de inactividad breve, que casi siempre ocurren a menos que un punto de referencia sintético que consuma todos los núcleos (por ejemplo, estrés ) Esta corriendo. Puede monitorear las transiciones de estado C, junto con otra información útil relacionada con las frecuencias del procesador, utilizando la maravillosa herramienta Google i7z en la mayoría de hardware compatible.

Para ver qué controlador de cpuidle está actualmente activo en su configuración, simplemente coloque el current_driverarchivo en la cpuidlesección de la /sys/devices/system/cpusiguiente manera:

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

Si desea que su sistema operativo Linux moderno tenga la latencia de cambio de contexto más baja posible, agregue los siguientes parámetros de arranque del kernel para deshabilitar todas estas funciones de ahorro de energía:

En Ubuntu 12.04, puede hacer esto agregándolos a la GRUB_CMDLINE_LINUX_DEFAULTentrada /etc/default/gruby luego ejecutándolos update-grub. Los parámetros de arranque para agregar son:

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

Aquí están los detalles sangrientos sobre lo que hacen las tres opciones de arranque:

Establecer intel_idle.max_cstateen cero revertirá su controlador cpuidle a acpi_idle(al menos según la documentación de la opción) o lo deshabilitará por completo. En mi caja está completamente deshabilitado (es decir, mostrar el current_driverarchivo /sys/devices/system/cpu/cpuidleproduce una salida de none). En este caso, la segunda opción de arranque no processor.max_cstate=0es necesaria. Sin embargo, la documentación indica que establecer max_cstate en cero para el intel_idlecontrolador debería revertir el sistema operativo al acpi_idlecontrolador. Por lo tanto, puse la segunda opción de arranque por si acaso.

La processor.max_cstateopción establece el estado C máximo para el acpi_idlecontrolador en cero, con suerte también lo desactiva. No tengo un sistema en el que pueda probar esto, porque intel_idle.max_cstate=0elimina por completo el controlador de cpuidle en todo el hardware disponible para mí. Sin embargo, si su instalación lo revierte de intel_idlea acpi_idlecon solo la primera opción de arranque, avíseme si la segunda opción processor.max_cstatehizo lo que estaba documentado en los comentarios para que pueda actualizar esta respuesta.

Finalmente, el último de los tres parámetros, idle=polles un verdadero acaparador de energía. Deshabilitará C1 / C1E, lo que eliminará el último bit restante de latencia a expensas de un consumo de energía mucho mayor, así que utilícelo solo cuando sea realmente necesario. Para la mayoría, esto será excesivo, ya que la latencia C1 * no es tan grande. Usando mi aplicación de prueba ejecutándose en el hardware que describí en la pregunta original, la latencia pasó de 9 a 3 us. Sin duda, esta es una reducción significativa para las aplicaciones sensibles a la latencia (por ejemplo, comercio financiero, telemetría / seguimiento de alta precisión, adquisición de datos de alta frecuencia, etc.), pero puede que no valga la pena el impacto de energía eléctrica incurrido para la gran mayoría de aplicaciones de escritorio. La única forma de saberlo con certeza es perfilar la mejora de rendimiento de su aplicación vs.

Actualizar:

Después de realizar pruebas adicionales con diferentes idle=*parámetros, he descubierto que el establecimiento idlede mwaitsi es compatible con su hardware es una idea mucho mejor. Parece que el uso de las MWAIT/MONITORinstrucciones permite que la CPU ingrese C1E sin que se agregue una latencia notable al tiempo de activación del hilo. Con idle=mwait, obtendrá temperaturas de CPU más frías (en comparación con idle=poll), menos uso de energía y aún conservará las excelentes latencias bajas de un bucle inactivo de sondeo. Por lo tanto, mi conjunto recomendado actualizado de parámetros de arranque para la latencia de activación de subprocesos de CPU baja basado en estos hallazgos es:

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

El uso de en idle=mwaitlugar de idle=polltambién puede ayudar con el inicio de Turbo Boost (al ayudar a que la CPU se mantenga por debajo de su TDP [Thermal Design Power]) y el hyperthreading (para lo cual MWAIT es el mecanismo ideal para no consumir un núcleo físico completo mientras se está tiempo evitando los estados C superiores). Sin embargo, esto aún no se ha probado en las pruebas, lo que seguiré haciendo.

Actualización 2:

La mwaitopción inactiva se ha eliminado de los kernels 3.x más nuevos (gracias al usuario ck_ por la actualización). Eso nos deja con dos opciones:

idle=halt- Debería funcionar tan bien como mwait, pero pruebe para asegurarse de que este sea el caso con su hardware. La HLTinstrucción es casi equivalente a una MWAITcon la sugerencia de estado 0. El problema radica en el hecho de que se requiere una interrupción para salir de un estado HLT, mientras que una escritura en memoria (o interrupción) se puede usar para salir del estado MWAIT. Dependiendo de lo que use el Kernel de Linux en su bucle inactivo, esto puede hacer que MWAIT sea potencialmente más eficiente. Entonces, como dije, pruebe / perfil y vea si satisface sus necesidades de latencia ...

y

idle=poll - La opción de mayor rendimiento, a expensas de la energía y el calor.

Michael Goldshteyn
fuente
Lo siento, pero ¿por qué esperaba que el firmware administrara los estados C? Los estados de suspensión son estados de tiempo de ejecución y son administrados por el sistema operativo por diseño. Como descubrió, si no desea suspender el tiempo de ejecución, no lo use.
Andy Ross
6
Lo siento, pero los estados C, EIST y C1E se pueden desactivar en el BIOS. Espero que el sistema operativo respete la configuración de mi BIOS. Esto es especialmente cierto, dadas las horrendas herramientas y documentación en este caso.
Michael Goldshteyn
4
Apagado a través de su BIOS, tal vez. No conozco nada en una especificación relevante que requiera eso. Lo siento, pero "esperar" cualquier cosa del BIOS te va a morder repetidamente. Lo mejor que puede hacer el firmware en una PC moderna es nada. Lamento que se haya sorprendido, pero, francamente, se trata de un error de usuario. Su punto de referencia medía los tiempos de suspensión y reanudación.
Andy Ross
19
Una de las funciones de la selección de funciones del BIOS es habilitar / deshabilitar dispositivos. En algunos casos, estas selecciones se fuerzan en el sistema operativo (por ejemplo, en la placa base USB, eSATA y NIC). En otros, se espera que el sistema operativo respete sus deseos (por ejemplo, EIST, estados C, Hyperthreading, Execute Disable, AES-NI, Virtualization, etc.). El BIOS proporciona una única superficie central de selección de funciones / dispositivos que es independiente del sistema operativo. Esto permite al usuario instalar varios sistemas operativos (quizás muy diferentes) en el host que utilizan las mismas características de hardware. Sin embargo, esta respuesta es subjetiva, por lo que tendrá que aceptar estar en desacuerdo.
Michael Goldshteyn
1
idle = mwait ya no es compatible con el kernel 3.x reciente lkml.org/lkml/2013/2/10/21 ¿ algún consejo alternativo?
ck_
8

Quizás lo que se volvió más lento es futex, el componente básico de las variables de condición. Esto arrojará algo de luz:

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

luego

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

que mostrará los microsegundos tomados para las llamadas al sistema interesantes, ordenados por tiempo.

En el 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

En el 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

Encontré este informe de error de hace 5 años que contiene una prueba de rendimiento de "ping pong" que compara

  1. mutex libpthread de un solo subproceso
  2. variable de condición libpthread
  3. señales viejas simples de Unix

Tuve que agregar

#include <stdint.h>

para compilar, lo que hice con este comando

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

En el 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.

En el 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.

Concluyo que entre el kernel 2.6.32 y 3.1.9 el cambio de contexto se ha ralentizado, aunque no tanto como se observa en el kernel 3.2. Me doy cuenta de que esto aún no responde a su pregunta, seguiré investigando.

Editar: Descubrí que cambiar la prioridad en tiempo real del proceso (ambos subprocesos) mejora el rendimiento en 3.1.9 para que coincida con 2.6.32. Sin embargo, establecer la misma prioridad en 2.6.32 lo ralentiza ... imagínate, lo investigaré más.

Aquí están mis resultados ahora:

En el 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.
$ 

En el 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
fuente
Lo ejecuté en Fedora y CentOS, no tengo Ubuntu. Publicaré mis resultados.
amdn
Bien, lo ejecuté en ambos hosts (es decir, y en diferentes núcleos) y los resultados no muestran casi ninguna disparidad. Entonces, esta prueba no destacó ninguna diferencia. El tiempo de llamada futex difiere en el cuarto decimal, una reducción insignificante en el rendimiento. Err espera, ¿los números enteros están en segundos? Acabo de ver que publicaste tus resultados y parecen similares a los míos ...
Michael Goldshteyn
Ok, eso descarta la implementación de futex; volvemos a su teoría del cambio de contexto ... no dude en eliminar esta respuesta ya que realmente pertenece a los comentarios ... Solo quería la capacidad de formatear los comandos.
amdn
Sí, los tiempos están en segundos ... las llamadas a futex que duran más de un segundo son para el hilo que espera la condición.
amdn
Entonces, ¿qué es lo que extrae de los resultados?
Michael Goldshteyn
1

También puede ver procesadores haciendo clic hacia abajo en procesos más recientes y kernels de Linux debido al controlador pstate que está separado de c-states. Entonces, además, para deshabilitar esto, usa el siguiente parámetro del kernel:

intel_pstate=disable

Kyle Brandt
fuente