¿Por qué el programador de Linux coloca dos subprocesos en el mismo núcleo físico en los procesadores con HyperThreading?
multithreading performance (3)
Citando su experiencia con dos procesadores adicionales que parecían funcionar correctamente, el i7-2600 y el Xeon E5-1620; Esto podría ser un tiro largo, pero ¿qué tal una actualización de microcódigo de la CPU? Podría incluir algo para solucionar el problema si es un comportamiento interno de la CPU.
Descargas de microcódigo de la CPU Intel: http://intel.ly/1aku6ak
También vea aquí: https://wiki.archlinux.org/index.php/Microcode
He leído en varios lugares que el programador predeterminado de Linux tiene un hipervínculo en las máquinas de varios núcleos, lo que significa que si tiene una máquina con 2 núcleos reales (4 HT), no programará dos hilos ocupados en núcleos lógicos de una manera que ambos se ejecutan en los mismos núcleos físicos (lo que llevaría a un costo de rendimiento 2x en muchos casos).
Pero cuando ejecuto stress -c 2
(genera dos subprocesos que se ejecutan en un 100% de CPU) en mi Intel i5-2520M, a menudo programa (y mantiene) los dos subprocesos en los núcleos HT 1 y 2, que se asignan al mismo núcleo físico . Incluso si el sistema está inactivo de lo contrario.
Esto también sucede con los programas reales (uso el stress
aquí porque hace que sea fácil de reproducir), y cuando eso sucede, es comprensible que mi programa tarde el doble de tiempo en ejecutarse. Establecer la afinidad manualmente con el conjunto de taskset
corrige eso para mi programa, pero yo esperaría que un programador con taskset
para HT lo hiciera correctamente por sí mismo.
Puede encontrar la configuración de HT-> Physical core con egrep "processor|physical id|core id" /proc/cpuinfo | sed ''s/^processor//nprocessor/g''
egrep "processor|physical id|core id" /proc/cpuinfo | sed ''s/^processor//nprocessor/g''
.
Entonces mi pregunta es: ¿Por qué el programador pone mis hilos en el mismo núcleo físico aquí?
Notas:
- Esta pregunta es muy similar a esta otra pregunta , cuyas respuestas dicen que Linux tiene un programador de subprocesos bastante sofisticado que es compatible con HT . Como se describió anteriormente, no puedo observar este hecho (compruebe usted mismo con el
stress -c
), y me gustaría saber por qué. - Sé que puedo establecer la afinidad de los procesadores manualmente para mis programas, por ejemplo, con la herramienta de conjunto de
taskset
o con la funciónsched_setaffinity
. Esto no es lo que estoy buscando, esperaría que el programador supiera por sí mismo que asignar dos subprocesos ocupados a un núcleo físico y dejar un núcleo físico completamente vacío no es una buena idea. - Soy consciente de que hay algunas situaciones en las que preferiría que los subprocesos se programen en el mismo núcleo físico y dejen el otro núcleo libre, pero parece absurdo que el programador haga eso aproximadamente en una cuarta parte de los casos. Me parece que los núcleos HT que selecciona son completamente aleatorios, o tal vez aquellos núcleos HT que tenían menos actividad en el momento de la programación, pero eso no sería muy consciente, dada la claridad con que los programas con las características del
stress
benefician de correr en núcleos físicos separados.
Creo que es hora de resumir algunos conocimientos de los comentarios.
El programador de Linux es consciente de HyperThreading: la información al respecto debe leerse en las tablas ACPI SRAT / SLIT, que se proporcionan en BIOS / UEFI, a partir de las cuales Linux crea dominios del programador .
Los dominios tienen jerarquía, es decir, en los servidores de 2 CPU obtendrás tres capas de dominios: all-cpus, per-cpu-package y per-cpu-core domain. Puedes comprobarlo desde /proc/schedstat
:
$ awk ''/^domain/ { print $1, $2; } /^cpu/ { print $1; }'' /proc/schedstat
cpu0
domain0 0000,00001001 <-- all cpus from core 0
domain1 0000,00555555 <-- all cpus from package 0
domain2 0000,00ffffff <-- all cpus in the system
Parte del programador de CFS es el equilibrador de carga, la bestia que debe robar tareas de su núcleo ocupado a otro núcleo. Aquí están sus descripciones de la documentación del Kernel:
Mientras hace eso, verifica si el dominio actual ha agotado su intervalo de reequilibrio. Si es así, ejecuta
load_balance()
en ese dominio. A continuación, verifica el sched_domain principal (si existe), y el principal del principal y así sucesivamente.Inicialmente,
load_balance()
encuentra el grupo más ocupado en el dominio de programación actual. Si tiene éxito, busca la cola de ejecución más ocupada de todas las colas de ejecución de la CPU en ese grupo. Si logra encontrar una cola de ejecución de este tipo, bloquea tanto la cola de ejecución de la CPU inicial como la cola de ejecución más reciente y comienza a mover las tareas de la misma a nuestra cola de ejecución. El número exacto de tareas equivale a un desequilibrio previamente calculado al iterar sobre los grupos de este dominio de programación.
Desde: https://www.kernel.org/doc/Documentation/scheduler/sched-domains.txt
Puede monitorear las actividades del equilibrador de carga comparando números en /proc/schedstat
. Escribí un script para hacer eso: schedstat.py
El contador alb_pushed
muestra que el equilibrador de carga se movió exitosamente de la tarea:
Sun Apr 12 14:15:52 2015 cpu0 cpu1 ... cpu6 cpu7 cpu8 cpu9 cpu10 ...
.domain1.alb_count ... 1 1 1
.domain1.alb_pushed ... 1 1 1
.domain2.alb_count 1 ...
.domain2.alb_pushed 1 ...
Sin embargo, la lógica del equilibrador de carga es compleja, por lo que es difícil determinar qué razones pueden impedirle hacer su trabajo bien y cómo se relacionan con los contadores de schedstat. Ni yo ni @thatotherguy pueden reproducir tu problema.
Veo dos posibilidades para ese comportamiento:
- Tiene una política agresiva de ahorro de energía que intenta guardar un núcleo para reducir el consumo de energía de la CPU.
- Realmente encontró un error con el subsistema de programación, de lo que debería ir a LKML y compartir cuidadosamente sus hallazgos (incluidos los datos de
mpstat
yschedstat
)
No puedo reproducir esto en 3.13.0-48 con mi CPU Intel (R) Xeon (R) E5-1650 0 @ 3.20GHz.
Tengo 6 núcleos con hyperthreading, donde el núcleo lógico N se asigna al núcleo físico N mod 6.
Aquí hay una salida típica de top
con stress -c 4
en dos columnas, de modo que cada fila es un núcleo físico (dejé algunos núcleos porque mi sistema no está inactivo):
%Cpu0 :100.0 us, %Cpu6 : 0.0 us,
%Cpu1 :100.0 us, %Cpu7 : 0.0 us,
%Cpu2 : 5.9 us, %Cpu8 : 2.0 us,
%Cpu3 :100.0 us, %Cpu9 : 5.7 us,
%Cpu4 : 3.9 us, %Cpu10 : 3.8 us,
%Cpu5 : 0.0 us, %Cpu11 :100.0 us,
Aquí está después de matar y reiniciar el stress
:
%Cpu0 :100.0 us, %Cpu6 : 2.6 us,
%Cpu1 :100.0 us, %Cpu7 : 0.0 us,
%Cpu2 : 0.0 us, %Cpu8 : 0.0 us,
%Cpu3 : 2.6 us, %Cpu9 : 0.0 us,
%Cpu4 : 0.0 us, %Cpu10 :100.0 us,
%Cpu5 : 2.6 us, %Cpu11 :100.0 us,
Hice esto varias veces, y no vi ningún caso en el que 4 subprocesos en 12 núcleos lógicos se programaran en el mismo núcleo físico.
Con -c 6
tiendo a obtener resultados como este, donde Linux parece estar ayudando a programar otros procesos en sus propios núcleos físicos. Aun así, se distribuyen mucho mejor que la casualidad:
%Cpu0 : 18.2 us, %Cpu6 : 4.5 us,
%Cpu1 : 0.0 us, %Cpu7 :100.0 us,
%Cpu2 :100.0 us, %Cpu8 :100.0 us,
%Cpu3 :100.0 us, %Cpu9 : 0.0 us,
%Cpu4 :100.0 us, %Cpu10 : 0.0 us,
%Cpu5 :100.0 us, %Cpu11 : 0.0 us,