Intel tuvo la amabilidad de responder a este problema. Vea su respuesta a continuación.
Este problema se debe a cómo se comprometen realmente las páginas físicas. En caso de páginas de 1GB, la memoria es contigua. Entonces, tan pronto como escriba en cualquier byte dentro de la página de 1 GB, se asigna la página completa de 1 GB. Sin embargo, con las páginas de 4 KB, las páginas físicas se asignan cuando toca por primera vez en cada una de las páginas de 4 KB.
for (uint64_t i = 0; i < size / MESSINESS_LEVEL / sizeof(*ptr); i++) {
for (uint64_t j = 0; j < MESSINESS_LEVEL; j++) {
index = i + j * size / MESSINESS_LEVEL / sizeof(*ptr);
ptr[index] = index * 5;
}
}
En el bucle más interno, el índice cambia a un ritmo de 512 KB. Entonces, las referencias consecutivas se asignan a compensaciones de 512 KB. Por lo general, los cachés tienen 2048 conjuntos (que son 2 ^ 11). Entonces, los bits 6:16 seleccionan los conjuntos. Pero si avanza con compensaciones de 512 KB, los bits 6:16 serían los mismos y terminarían seleccionando el mismo conjunto y perdiendo la localidad espacial.
Recomendamos inicializar todo el búfer de 1 GB secuencialmente (en la prueba de página pequeña) como se muestra a continuación antes de iniciar el reloj para cronometrarlo
for (uint64_t i = 0; i < size / sizeof(*ptr); i++)
ptr[i] = i * 5;
Básicamente, el problema es con los conflictos de conjuntos que resultan en fallas de caché en el caso de páginas grandes en comparación con páginas pequeñas debido a desplazamientos constantes muy grandes. Cuando usa compensaciones constantes, la prueba realmente no es aleatoria .
No es una respuesta, sino proporcionar más detalles sobre este tema desconcertante.
Los contadores de rendimiento muestran aproximadamente una cantidad similar de instrucciones, pero aproximadamente el doble de la cantidad de ciclos utilizados cuando se usan páginas grandes:
- 4KiB páginas IPC 0.29,
- 1GiB páginas IPC 0.10.
Estos números de IPC dicen que el código tiene un cuello de botella en el acceso a la memoria (el IPC vinculado a la CPU en Skylake es 3 y superior). Las páginas enormes hacen más difícil el cuello de botella.
Modifiqué su punto de referencia para usar MAP_POPULATE | MAP_LOCKED | MAP_FIXED
con dirección fija 0x600000000000
para ambos casos para eliminar la variación de tiempo asociada con fallas de página y asignación aleatoria de direcciones. En mi sistema Skylake, 2 MiB y 1 GiB son más del doble de lentos que las páginas de 4 kiB.
Compilado con g++-8.4.0 -std=gnu++14 -pthread -m{arch,tune}=skylake -O3 -DNDEBUG
:
[[email protected]:~/src/test] $ sudo hugeadm --pool-pages-min 2MB:64 --pool-pages-max 2MB:64
[[email protected]:~/src/test] $ sudo hugeadm --pool-pages-min 1GB:1 --pool-pages-max 1GB:1
[[email protected]:~/src/test] $ for s in small huge; do sudo chrt -f 40 taskset -c 7 perf stat -dd ./release/gcc/test $s random; done
Duration: 2156150
Performance counter stats for './release/gcc/test small random':
2291.190394 task-clock (msec) # 1.000 CPUs utilized
1 context-switches # 0.000 K/sec
0 cpu-migrations # 0.000 K/sec
53 page-faults # 0.023 K/sec
11,448,252,551 cycles # 4.997 GHz (30.83%)
3,268,573,978 instructions # 0.29 insn per cycle (38.55%)
430,248,155 branches # 187.784 M/sec (38.55%)
758,917 branch-misses # 0.18% of all branches (38.55%)
224,593,751 L1-dcache-loads # 98.025 M/sec (38.55%)
561,979,341 L1-dcache-load-misses # 250.22% of all L1-dcache hits (38.44%)
271,067,656 LLC-loads # 118.309 M/sec (30.73%)
668,118 LLC-load-misses # 0.25% of all LL-cache hits (30.73%)
<not supported> L1-icache-loads
220,251 L1-icache-load-misses (30.73%)
286,864,314 dTLB-loads # 125.203 M/sec (30.73%)
6,314 dTLB-load-misses # 0.00% of all dTLB cache hits (30.73%)
29 iTLB-loads # 0.013 K/sec (30.73%)
6,366 iTLB-load-misses # 21951.72% of all iTLB cache hits (30.73%)
2.291300162 seconds time elapsed
Duration: 4349681
Performance counter stats for './release/gcc/test huge random':
4385.282466 task-clock (msec) # 1.000 CPUs utilized
1 context-switches # 0.000 K/sec
0 cpu-migrations # 0.000 K/sec
53 page-faults # 0.012 K/sec
21,911,541,450 cycles # 4.997 GHz (30.70%)
2,175,972,910 instructions # 0.10 insn per cycle (38.45%)
274,356,392 branches # 62.563 M/sec (38.54%)
560,941 branch-misses # 0.20% of all branches (38.63%)
7,966,853 L1-dcache-loads # 1.817 M/sec (38.70%)
292,131,592 L1-dcache-load-misses # 3666.84% of all L1-dcache hits (38.65%)
27,531 LLC-loads # 0.006 M/sec (30.81%)
12,413 LLC-load-misses # 45.09% of all LL-cache hits (30.72%)
<not supported> L1-icache-loads
353,438 L1-icache-load-misses (30.65%)
7,252,590 dTLB-loads # 1.654 M/sec (30.65%)
440 dTLB-load-misses # 0.01% of all dTLB cache hits (30.65%)
274 iTLB-loads # 0.062 K/sec (30.65%)
9,577 iTLB-load-misses # 3495.26% of all iTLB cache hits (30.65%)
4.385392278 seconds time elapsed
Se ejecutó en Ubuntu 18.04.5 LTS con Intel i9-9900KS (que no es NUMA), 4x8GiB 4GHz CL17 RAM en las 4 ranuras, con performance
regulador para no escalar la frecuencia de la CPU, ventiladores de refrigeración líquida al máximo para no estrangulamiento térmico, prioridad FIFO 40 para no preferencia, en un núcleo de CPU específico para no migrar la CPU, varias ejecuciones. Los resultados son similares con clang++-8.0.0
compilador.
Parece que hay algo sospechoso en el hardware, como un búfer de almacenamiento por marco de página, por lo que las páginas de 4 KiB permiten ~2 veces más almacenamiento por unidad de tiempo.
Sería interesante ver los resultados de las CPU AMD Ryzen 3.
En AMD Ryzen 3 5950X, la versión de páginas grandes es solo un 10 % más lenta:
Duration: 1578723
Performance counter stats for './release/gcc/test small random':
1,726.89 msec task-clock # 1.000 CPUs utilized
0 context-switches # 0.000 K/sec
0 cpu-migrations # 0.000 K/sec
1,947 page-faults # 0.001 M/sec
8,189,576,204 cycles # 4.742 GHz (33.02%)
3,174,036 stalled-cycles-frontend # 0.04% frontend cycles idle (33.14%)
95,950 stalled-cycles-backend # 0.00% backend cycles idle (33.25%)
3,301,760,473 instructions # 0.40 insn per cycle
# 0.00 stalled cycles per insn (33.37%)
480,276,481 branches # 278.116 M/sec (33.49%)
864,075 branch-misses # 0.18% of all branches (33.59%)
709,483,403 L1-dcache-loads # 410.844 M/sec (33.59%)
1,608,181,551 L1-dcache-load-misses # 226.67% of all L1-dcache accesses (33.59%)
<not supported> LLC-loads
<not supported> LLC-load-misses
78,963,441 L1-icache-loads # 45.726 M/sec (33.59%)
46,639 L1-icache-load-misses # 0.06% of all L1-icache accesses (33.51%)
301,463,437 dTLB-loads # 174.570 M/sec (33.39%)
301,698,272 dTLB-load-misses # 100.08% of all dTLB cache accesses (33.28%)
54 iTLB-loads # 0.031 K/sec (33.16%)
2,774 iTLB-load-misses # 5137.04% of all iTLB cache accesses (33.05%)
243,732,886 L1-dcache-prefetches # 141.140 M/sec (33.01%)
<not supported> L1-dcache-prefetch-misses
1.727052901 seconds time elapsed
1.579089000 seconds user
0.147914000 seconds sys
Duration: 1628512
Performance counter stats for './release/gcc/test huge random':
1,680.06 msec task-clock # 1.000 CPUs utilized
1 context-switches # 0.001 K/sec
1 cpu-migrations # 0.001 K/sec
1,947 page-faults # 0.001 M/sec
8,037,708,678 cycles # 4.784 GHz (33.34%)
4,684,831 stalled-cycles-frontend # 0.06% frontend cycles idle (33.34%)
2,445,415 stalled-cycles-backend # 0.03% backend cycles idle (33.34%)
2,217,699,442 instructions # 0.28 insn per cycle
# 0.00 stalled cycles per insn (33.34%)
281,522,918 branches # 167.567 M/sec (33.34%)
549,427 branch-misses # 0.20% of all branches (33.33%)
312,930,677 L1-dcache-loads # 186.261 M/sec (33.33%)
1,614,505,314 L1-dcache-load-misses # 515.93% of all L1-dcache accesses (33.33%)
<not supported> LLC-loads
<not supported> LLC-load-misses
888,872 L1-icache-loads # 0.529 M/sec (33.33%)
13,140 L1-icache-load-misses # 1.48% of all L1-icache accesses (33.33%)
9,168 dTLB-loads # 0.005 M/sec (33.33%)
870 dTLB-load-misses # 9.49% of all dTLB cache accesses (33.33%)
1,173 iTLB-loads # 0.698 K/sec (33.33%)
1,914 iTLB-load-misses # 163.17% of all iTLB cache accesses (33.33%)
253,307,275 L1-dcache-prefetches # 150.772 M/sec (33.33%)
<not supported> L1-dcache-prefetch-misses
1.680230802 seconds time elapsed
1.628170000 seconds user
0.052005000 seconds sys