kid3 easytag linux multithreading linux-kernel kernel ubuntu-12.04

linux - kid3 - easytag



El contexto cambia mucho más lentamente en los nuevos núcleos de Linux (3)

La solución al problema de rendimiento de intel_idle del intel_idle defectuoso en núcleos recientes tiene que ver con el cambio al controlador intel_idle cpuidle de acpi_idle , el controlador utilizado en núcleos más antiguos. Lamentablemente, el controlador intel_idle ignora la configuración del BIOS del usuario para los C-estados y baila a su propia melodía . En otras palabras, incluso si deshabilita completamente todos los estados C en el BIOS de su PC (o del servidor), este controlador los forzará durante períodos de inactividad breve, que casi siempre suceden a menos que sea un punto de referencia sintético que consume todos los núcleos (por ejemplo, estrés ) Esta corriendo. Puede controlar 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 los hardware compatibles.

Para ver qué controlador cpuidle está actualmente activo en su configuración, solo cat el archivo current_driver en la sección cpuidle de /sys/devices/system/cpu siguiente manera:

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

Si desea que su sistema operativo Linux moderno tenga la latencia de conmutación de contexto más baja posible, agregue los siguientes parámetros de arranque del kernel para desactivar todas estas características de ahorro de energía:

En Ubuntu 12.04, puedes hacer esto agregándolos a la entrada GRUB_CMDLINE_LINUX_DEFAULT en /etc/default/grub y luego ejecutando 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_cstate en cero revertirá su controlador cpuidle a acpi_idle (al menos según la documentación de la opción) o lo desactivará por completo. En mi caja está completamente deshabilitado (es decir, mostrar el archivo current_driver en /sys/devices/system/cpu/cpuidle produce una salida de none ). En este caso, la segunda opción de arranque, processor.max_cstate=0 es innecesaria. Sin embargo, la documentación indica que establecer max_cstate en cero para el controlador intel_idle debe revertir el sistema operativo al controlador acpi_idle . Por lo tanto, puse la segunda opción de arranque por si acaso.

La opción processor.max_cstate establece el estado C máximo para el controlador acpi_idle en cero, con suerte también lo deshabilita. No tengo un sistema en el que pueda probar esto porque intel_idle.max_cstate=0 por completo el controlador cpuidle en todo el hardware disponible para mí. Sin embargo, si su instalación lo revierte de intel_idle a acpi_idle con solo la primera opción de inicio, acpi_idle si la segunda opción, processor.max_cstate , hizo lo que estaba documentado hacer en los comentarios para que yo pueda actualizar esta respuesta.

Finalmente, el último de los tres parámetros, idle=poll es una fuente de poder real. Deshabilitará C1 / C1E, lo que eliminará el último bit de latencia restante a expensas de mucho más consumo de energía, por lo tanto, use ese 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 us a 3 us. Esto es ciertamente una reducción significativa para aplicaciones sensibles a la alta latencia (por ejemplo, comercio financiero, telemetría / seguimiento de alta precisión, adquisición de datos de alta frecuencia, etc.), pero puede no valer la energía eléctrica incurrida 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 frente al aumento real en el consumo / calor de su hardware y sopesar las compensaciones.

Actualizar:

Después de pruebas adicionales con varios parámetros idle=* , descubrí que la configuración de idle a mwait si es compatible con su hardware es una idea mucho mejor. Parece que el uso de las instrucciones MWAIT/MONITOR permite que la CPU ingrese C1E sin que se agregue ninguna latencia notable al tiempo de MWAIT/MONITOR de la MWAIT/MONITOR . Con idle=mwait , obtendrá temperaturas más bajas de la CPU (en comparación con idle=poll ), menos uso de energía y aún conservará las latencias bajas excelentes de un bucle de inactividad de sondeo. Por lo tanto, mi conjunto recomendado de parámetros de arranque para baja latencia de activación de subprocesos de CPU basados ​​en estos hallazgos es:

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

El uso de idle=mwait lugar de idle=poll también puede ayudar con el inicio de Turbo Boost (ayudando a la CPU a mantenerse por debajo de su TDP [Thermal Design Power]) y hyperthreading (para lo cual MWAIT es el mecanismo ideal para no consumir todo núcleo físico mientras que al mismo tiempo evita los estados C más altos). Esto aún no se ha probado en las pruebas, sin embargo, lo cual continuaré haciendo.

Actualización 2:

La opción mwait inactivo se ha eliminado de los núcleos 3.x más nuevos (gracias al usuario ck_ para la actualización). Eso nos deja con dos opciones:

idle=halt : debe funcionar tan bien como mwait , pero pruebe para asegurarse de que este sea el caso con su hardware. La instrucción HLT es casi equivalente a un MWAIT con indicación 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 de memoria (o interrupción) se puede usar para salir del MWAIT estado. Dependiendo de lo que use el núcleo de Linux en su ciclo inactivo, esto puede hacer que MWAIT sea potencialmente más eficiente. Entonces, como dije test / profile y veo si cumple con tus necesidades de latencia ...

y

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

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 convertido en ejecutable se ha incrementado 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 hilos. El primer hilo obtiene la hora actual (en ticks usando RDTSC) y luego señala una variable de condición una vez por segundo. El segundo subproceso espera en la variable de condición y se activa cuando se señaliza. Luego obtiene la hora actual (en ticks usando RDTSC). La diferencia entre el tiempo en el segundo subproceso y el tiempo en el primer subproceso se calcula y se muestra en la consola. Después de esto, el segundo subproceso espera una vez más en la variable de condición. Será señalado nuevamente por el primer hilo después de aproximadamente un segundo pase.

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

En kernel 2.6.32, esta latencia está en el orden de 2.8-3.5 us, lo cual es razonable. En kernel 3.2.0, esta latencia se ha incrementado en algún lugar del orden de 40-100 us. He excluido cualquier diferencia en el hardware entre los dos hosts. Se ejecutan en hardware idéntico (procesadores de doble socket X5687 {Westmere-EP} que funcionan a 3.6 GHz con hyperthreading, speedstep y todos los estados C apagados). 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 rebote de subprocesos en núcleos o rebote / comunicación entre tomas.

La única diferencia entre los dos hosts es que uno está ejecutando Ubuntu 10.04 LTS con kernel 2.6.32-28 (el cuadro de cambio de contexto rápido) y el otro está ejecutando 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 desaceleración en cuanto a cuánto tiempo 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 para pegar para su lectura. Compilar con:

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

Ejecutar con (suponiendo que tiene al menos un cuadro 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 mucha búsqueda a través de 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.


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

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

entonces

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 interesantes llamadas al sistema, ordenadas por tiempo.

En 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 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 errores de 5 años que contiene una prueba de rendimiento "ping pong" que compara

  1. mutex libpthread de subproceso único
  2. variable de condición libpthread
  3. simples señales de Unix antiguas

Tuve que agregar

#include <stdint.h>

para compilar, lo cual hice con este comando

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

En 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 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 kernel 2.6.32 y 3.1.9, el cambio de contexto se ha ralentizado, aunque no tanto como se observa en kernel 3.2. Me doy cuenta de que esto aún no responde su pregunta, seguiré cavando.

Editar: he descubierto que cambiar la prioridad en tiempo real del proceso (ambos hilos) mejora el rendimiento en 3.1.9 para que coincida con 2.6.32. Sin embargo, establecer la misma prioridad en 2.6.32 hace que se ralentice ... vaya figura - Lo investigaré más.

Aquí están mis resultados ahora:

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


También puede ver procesadores haciendo clic 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, tiene el siguiente parámetro kernel:

intel_pstate=disable