Hice hincapié en mi sistema para ver cómo afecta algún programa que escribí usando stress-ng.

El programa en sí es una red neuronal, compuesta principalmente por algunos bucles anidados que hacen algunas multiplicaciones y usan aproximadamente 1G de RAM codificada en C ++.

Impuse algo de estrés de memoria en el sistema usando:

stress-ng --vm 4 --vm-bytes 2G -t 100s

Que crea 4 trabajadores girando en mmap asignando 2G de RAM cada uno. Esto ralentiza significativamente la ejecución de mi programa (de aproximadamente 150 ms a 250 ms). Pero la razón por la que el programa se ralentiza no es la falta de memoria o ancho de banda de memoria o algo así. En cambio, los ciclos de la CPU disminuyen de 3.4GHz (sin stress-ng) a 2.8GHz (con stress-ng). La utilización de la CPU se mantiene aproximadamente igual (99%), como se esperaba.

Medí la frecuencia de la CPU usando

sudo perf stat -B ./my_program

¿Alguien sabe por qué el estrés de la memoria ralentiza la CPU?

Mi CPU es un Intel (R) Core (TM) i5-8250U y mi sistema operativo es Ubuntu 18.04.

Saludos cordiales lpolari

2
L Polari 13 ago. 2020 a las 19:31

3 respuestas

La mejor respuesta

Las CPU derivadas de Skylake reducen la velocidad de su reloj central cuando tienen cuello de botella en la carga / almacenamiento, en configuraciones de energía frente a rendimiento que favorecen un mayor ahorro de energía. Sorprendentemente, puede construir casos artificiales en los que esta reducción de reloj ocurre incluso con tiendas que se encuentran en la caché L1d, o cargas desde la memoria no inicializada (aún CoW mapeado en las mismas páginas cero).

Skylake introdujo el control de hardware completo de la frecuencia de la CPU (estado P de hardware = HWP). https: //unix.stackexchange. com / preguntas / 439340 / ¿cuáles-son-las-implicaciones-de-configurar-el-gobernador-de-la-cpu-a-rendimiento La decisión de frecuencia puede tomar en cuenta el monitoreo de desempeño interno que puede notar cosas como pasar la mayoría de los ciclos estancados , o en lo que está estancado. No sé qué heurística usa exactamente Skylake.

Puede reproducir este 1 recorriendo una matriz grande sin realizar ninguna llamada al sistema. Si es grande (o atraviesa líneas de caché en una prueba artificial), perf stat ./a.out mostrará que la velocidad promedio del reloj es menor que para los bucles normales de CPU.


En teoría, si la memoria no sigue el ritmo de la CPU, reducir la velocidad del reloj del núcleo (y mantener constante el controlador de memoria) no debería afectar mucho al rendimiento. En la práctica, reducir la velocidad del reloj también reduce la velocidad del reloj sin núcleo (bus de anillo + caché L3), lo que también empeora un poco la latencia de la memoria y el ancho de banda.

Parte de la latencia de una falta de caché es obtener la solicitud del núcleo de la CPU al controlador de memoria, y el ancho de banda de un solo núcleo está limitado por la concurrencia máxima (solicitudes pendientes que un núcleo puede rastrear) / latencia. ¿Por qué Skylake es tan mucho mejor que Broadwell-E para el rendimiento de la memoria de un solo subproceso?

P.ej. mi i7-6700k cae de 3.9GHz a 2.7GHz cuando se ejecuta un microbenchmark que solo crea cuellos de botella en la DRAM en la configuración de arranque predeterminada. (También solo sube a 3.9GHz en lugar de 4.0 all-core o 4.2GHz con 1 o 2 núcleos activos como se configura en el BIOS, con la configuración predeterminada de EPP balance_power en el arranque o con balance_performance. )

Este valor predeterminado no parece muy bueno, demasiado conservador para los chips "cliente" donde un solo núcleo puede casi saturar el ancho de banda de la DRAM, pero solo a la velocidad de reloj completa. O demasiado agresivo sobre el ahorro de energía, si lo miras desde el otro punto de vista, especialmente para chips como mi computadora de escritorio con un TDP alto (95W) que puede mantener la velocidad de reloj completa indefinidamente incluso cuando se ejecutan cosas que consumen mucha energía, como la codificación de video x265, que hace un uso intensivo de AVX2.

Podría tener más sentido con un chip ULV de 15 W como el i5-8250U para intentar dejar más margen térmico / energético para cuando la CPU esté haciendo algo más interesante.


Esto se rige por su configuración de Preferencia de energía / rendimiento (EPP) . Ocurre con bastante fuerza en la configuración predeterminada balance_power. No sucede en absoluto performance, y algunas comparativas rápidas indican que balance_performance también evita esta desaceleración que ahorra energía. Utilizo balance_performance en mi escritorio.

Los chips "Client" (no Xeon) antes de Ice Lake tienen todos los núcleos bloqueados para que funcionen a la misma velocidad de reloj (y todos funcionarán más alto si incluso uno de ellos ejecuta algo que no está limitado a la memoria, como un while(1) { _mm_pause(); } lazo). Pero todavía hay una configuración de EPP para cada núcleo lógico. Siempre he cambiado la configuración de todos los núcleos para mantenerlos iguales:

En Linux, leyendo la configuración:

$ grep . /sys/devices/system/cpu/cpufreq/policy[0-9]*/energy_performance_preference
/sys/devices/system/cpu/cpufreq/policy0/energy_performance_preference:balance_performance
/sys/devices/system/cpu/cpufreq/policy1/energy_performance_preference:balance_performance
...
/sys/devices/system/cpu/cpufreq/policy7/energy_performance_preference:balance_performance

Escribir la configuración:

sudo sh -c 'for i in /sys/devices/system/cpu/cpufreq/policy[0-9]*/energy_performance_preference;
 do echo balance_performance > "$i"; done'

Véase también


Nota al pie 1: ejemplo experimental:

Almacene 1 dword por línea de caché, avanzando a través de las líneas de caché contiguas hasta el final del búfer, luego regrese el puntero al inicio. Repita para un número fijo de tiendas, independientemente del tamaño del búfer.

;; t=testloop; nasm -felf64 "$t.asm" && ld "$t.o" -o "$t" && taskset -c 3 perf stat -d -etask-clock,context-switches,cpu-migrations,page-faults,cycles,instructions,uops_issued.any,uops_executed.thread ./"$t"

;; nasm -felf64 testloop.asm
;; ld -o testloop testloop.o
;; taskset -c 3 perf stat -etask-clock,context-switches,cpu-migrations,page-faults,cycles,instructions,uops_issued.any,uops_executed.thread -r1 ./testloop

; or idq.mite_uops 

default rel
%ifdef __YASM_VER__
;    CPU intelnop
;    CPU Conroe AMD
    CPU Skylake AMD
%else
%use smartalign
alignmode p6, 64
%endif

global _start
_start:

    lea        rdi, [buf]
    lea        rsi, [endbuf]
;    mov        rsi, qword endbuf           ; large buffer.  NASM / YASM can't actually handle a huge BSS and hit a failed assert (NASM) or make a binary that doesn't reserve enough BSS space.

    mov     ebp, 1000000000

align 64
.loop:
%if 0
      mov  eax, [rdi]              ; LOAD
      mov  eax, [rdi+64]
%else
      mov  [rdi], eax              ; STORE
      mov  [rdi+64], eax
%endif
    add  rdi, 128
    cmp  rdi, rsi
    jae  .wrap_ptr        ; normally falls through, total loop = 4 fused-domain uops
 .back:

    dec ebp
    jnz .loop
.end:

    xor edi,edi
    mov eax,231   ; __NR_exit_group  from /usr/include/asm/unistd_64.h
    syscall       ; sys_exit_group(0)

.wrap_ptr:
   lea  rdi, [buf]
   jmp  .back


section .bss
align 4096
;buf:    resb 2048*1024*1024 - 1024*1024     ; just under 2GiB so RIP-rel still works
buf:    resb 1024*1024 / 64     ; 16kiB = half of L1d

endbuf:
  resb 4096        ; spare space to allow overshoot

Sistema de prueba: Arch GNU / Linux, kernel 5.7.6-arch1-1. (Y NASM 2.14.02, ld de GNU Binutils 2.34.0).

  • CPU: i7-6700k Skylake
  • placa base: Asus Z170 Pro Gaming, configurada en BIOS para 1 o 2 núcleos turbo = 4.2GHz, 3 o 4 núcleos = 4.0GHz. Pero la configuración de EPP predeterminada en el arranque es balance_power, que solo llega hasta los 3.9GHz. Mi secuencia de comandos de arranque cambia a balance_pwerformance, que todavía solo llega a 3.9GHz para que los fanáticos se mantengan en silencio, pero es menos conservador.
  • DRAM: DDR4-2666 (irrelevante para esta pequeña prueba sin fallas de caché).

Hyperthreading está habilitado, pero el sistema está inactivo y el kernel no programará nada en el otro núcleo lógico (el hermano del que lo fijé), por lo que tiene un núcleo físico para sí mismo.

Sin embargo, esto significa que perf no está dispuesto a usar más contadores de rendimiento programables para un subproceso, por lo que perf stat -d para monitorear las cargas y el reemplazo de L1d, y L3 hit / miss significaría una medición menos precisa para cycles y así sucesivamente. Es insignificante, como 424k L1-dcache-cargas (probablemente en controladores de fallas de página del kernel, controladores de interrupciones y otros gastos generales, porque el bucle no tiene cargas). L1-dcache-load-misses es en realidad L1D.REPLACEMENT y es incluso más bajo, como 48k

Usé algunos eventos de rendimiento, incluidos exe_activity.bound_on_stores - [Ciclos donde el búfer de tienda estaba lleno y sin carga pendiente]. (Consulte perf list para obtener descripciones y / o los manuales de Intel para obtener más información).

EPP: balance_power: reloj descendente de 2,7 GHz fuera de 3,9 GHz

Configuración de EPP: balance_power con sudo sh -c 'for i in /sys/devices/system/cpu/cpufreq/policy[0-9]*/energy_performance_preference;do echo balance_power > "$i";done'

Hay limitaciones basadas en lo que está haciendo el código; con un bucle de pausa en otro núcleo manteniendo los relojes altos, esto se ejecutaría más rápido en este código. O con diferentes instrucciones en el bucle.

# sudo ... balance_power
$ taskset -c 3 perf stat -etask-clock:u,task-clock,context-switches,cpu-migrations,page-faults,cycles,branches,instructions,uops_issued.any,uops_executed.thread,exe_activity.bound_on_stores -r1 ./"$t" 

 Performance counter stats for './testloop':

            779.56 msec task-clock:u              #    1.000 CPUs utilized          
            779.56 msec task-clock                #    1.000 CPUs utilized          
                 3      context-switches          #    0.004 K/sec                  
                 0      cpu-migrations            #    0.000 K/sec                  
                 6      page-faults               #    0.008 K/sec                  
     2,104,778,670      cycles                    #    2.700 GHz                    
     2,008,110,142      branches                  # 2575.962 M/sec                  
     7,017,137,958      instructions              #    3.33  insn per cycle         
     5,217,161,206      uops_issued.any           # 6692.465 M/sec                  
     7,191,265,987      uops_executed.thread      # 9224.805 M/sec                  
       613,076,394      exe_activity.bound_on_stores #  786.442 M/sec                  

       0.779907034 seconds time elapsed

       0.779451000 seconds user
       0.000000000 seconds sys

Por casualidad, esto pasó a obtener exactamente 2.7GHz. Por lo general, hay algo de ruido o sobrecarga de inicio y es un poco menor. Tenga en cuenta que 5217951928 uops de front-end / 2106180524 ciclos = ~ 2.48 uops promedio emitidos por ciclo, de un ancho de canalización de 4, por lo que este no es un código de bajo rendimiento. El recuento de instrucciones es mayor debido a la comparación / bifurcación fusionada con macros. (Podría haber desenrollado más, así que incluso más instrucciones fueron tiendas, menos agregar y ramificar, pero no lo hice).

(Volví a ejecutar el comando perf stat un par de veces para que la CPU no se estuviera despertando de la suspensión de bajo consumo al comienzo del intervalo cronometrado. Todavía hay fallas de página en el intervalo, pero hay fallas de 6 páginas insignificante en un punto de referencia de 3/4 de segundo).

balance_performance: 3,9 GHz completos, velocidad máxima para este EPP

Sin limitación basada en lo que hace el código.

# sudo ... balance_performance
$ taskset -c 3 perf stat -etask-clock:u,task-clock,context-switches,cpu-migrations,page-faults,cycles,branches,instructions,uops_issued.any,uops_executed.thread,exe_activity.bound_on_stores -r1 ./"$t" 

 Performance counter stats for './testloop':

            539.83 msec task-clock:u              #    0.999 CPUs utilized          
            539.83 msec task-clock                #    0.999 CPUs utilized          
                 3      context-switches          #    0.006 K/sec                  
                 0      cpu-migrations            #    0.000 K/sec                  
                 6      page-faults               #    0.011 K/sec                  
     2,105,328,671      cycles                    #    3.900 GHz                    
     2,008,030,096      branches                  # 3719.713 M/sec                  
     7,016,729,050      instructions              #    3.33  insn per cycle         
     5,217,686,004      uops_issued.any           # 9665.340 M/sec                  
     7,192,389,444      uops_executed.thread      # 13323.318 M/sec                 
       626,115,041      exe_activity.bound_on_stores # 1159.827 M/sec                  

       0.540108507 seconds time elapsed

       0.539877000 seconds user
       0.000000000 seconds sys

Aproximadamente lo mismo en el reloj por reloj, aunque un poco más de ciclos totales donde el búfer de almacenamiento estaba lleno. (Eso está entre el núcleo y la caché L1d, no fuera del núcleo, por lo que esperaríamos lo mismo para el bucle en sí. Usando -r10 para repetir 10 veces, ese número es estable + - 0.01% entre ejecuciones).

performance: 4,2 GHz, turbo completo a la frecuencia configurada más alta

Sin limitación basada en lo que hace el código.

# sudo ... performance
taskset -c 3 perf stat -etask-clock,context-switches,cpu-migrations,page-faults,cycles,instructions,uops_issued.any,uops_executed.thread -r1 ./testloop

 Performance counter stats for './testloop':

            500.95 msec task-clock:u              #    1.000 CPUs utilized          
            500.95 msec task-clock                #    1.000 CPUs utilized          
                 0      context-switches          #    0.000 K/sec                  
                 0      cpu-migrations            #    0.000 K/sec                  
                 7      page-faults               #    0.014 K/sec                  
     2,098,112,999      cycles                    #    4.188 GHz                    
     2,007,994,492      branches                  # 4008.380 M/sec                  
     7,016,551,461      instructions              #    3.34  insn per cycle         
     5,217,839,192      uops_issued.any           # 10415.906 M/sec                 
     7,192,116,174      uops_executed.thread      # 14356.978 M/sec                 
       624,662,664      exe_activity.bound_on_stores # 1246.958 M/sec                  

       0.501151045 seconds time elapsed

       0.501042000 seconds user
       0.000000000 seconds sys

El rendimiento general se escala linealmente con la velocidad del reloj, por lo que esta es una aceleración de ~ 1.5x en comparación con balance_power. (1,44 para balance_performance que tiene la misma velocidad de reloj completa de 3,9 GHz).

Con búferes lo suficientemente grandes como para causar fallas de caché L1d o L2, todavía hay una diferencia en los ciclos de reloj del núcleo.

5
Peter Cordes 15 ago. 2020 a las 22:28

La última vez que miré esto, estaba habilitando la configuración de "Turbo de eficiencia energética" que permitía al procesador hacer esto. En términos generales, el hardware monitorea las instrucciones por ciclo y se abstiene de continuar aumentando la frecuencia Turbo si el aumento de frecuencia no da como resultado un aumento adecuado del rendimiento. Para el punto de referencia STREAM, la frecuencia típicamente bajó algunos bins, pero el rendimiento estuvo dentro del 1% del rendimiento asintótico.

No sé si Intel ha documentado cómo la configuración de "Turbo de eficiencia energética" interactúa con todas las variantes de "Preferencia de rendimiento energético". En nuestros sistemas de producción, "Energy Efficient Turbo" está desactivado en el BIOS, pero a veces está activado de forma predeterminada ...

2
John D McCalpin 15 ago. 2020 a las 16:50

Es importante recordar que las CPU modernas, especialmente las fabricadas por Intel, tienen frecuencias de reloj variables. La CPU funcionará lentamente cuando esté ligeramente cargada para ahorrar energía, lo que prolonga la vida útil de la batería, pero puede aumentar bajo carga.

El factor limitante son las térmicas , es decir, a la CPU solo se le permitirá calentarse tanto antes de recortar la frecuencia para reducir el consumo de energía y, por extensión, la generación de calor.

En un chip con más de un núcleo, se puede ejecutar un solo núcleo muy rápidamente sin afectar la regulación térmica. Dos núcleos deben funcionar más lento, están produciendo efectivamente el doble de calor, y cuando se usan los cuatro núcleos, cada uno tiene que compartir una porción más pequeña del presupuesto térmico general.

Vale la pena verificar la temperatura de su CPU mientras se realizan las pruebas, ya que es probable que alcance algún tipo de límite.

2
tadman 13 ago. 2020 a las 16:56