Intel war so freundlich, auf dieses Problem zu antworten. Siehe ihre Antwort unten.
Dieses Problem ist darauf zurückzuführen, wie physische Seiten tatsächlich festgeschrieben werden. Bei 1-GB-Seiten ist der Speicher zusammenhängend. Sobald Sie also auf ein beliebiges Byte innerhalb der 1-GB-Seite schreiben, wird die gesamte 1-GB-Seite zugewiesen. Bei 4-KB-Seiten werden die physischen Seiten jedoch zugewiesen, sobald Sie die 4-KB-Seiten zum ersten Mal berühren.
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;
}
}
In der innersten Schleife ändert sich der Index mit einer Schrittweite von 512 KB. Aufeinanderfolgende Referenzen werden also bei 512-KB-Offsets abgebildet. Typischerweise haben Caches 2048 Sets (das sind 2^11). Also, Bits 6:16 wählen die Sets aus. Aber wenn Sie bei 512 KB-Offsets schreiten, wären die Bits 6:16 dieselben, was dazu führen würde, dass Sie denselben Satz auswählen und die räumliche Lokalität verlieren.
Wir empfehlen, den gesamten 1-GB-Puffer sequenziell (im kleinen Seitentest) wie unten zu initialisieren, bevor Sie die Uhr starten, um ihn zu timen
for (uint64_t i = 0; i < size / sizeof(*ptr); i++)
ptr[i] = i * 5;
Grundsätzlich besteht das Problem in Set-Konflikten, die bei großen Seiten im Vergleich zu kleinen Seiten aufgrund sehr großer konstanter Offsets zu Cache-Fehlern führen. Wenn Sie konstante Offsets verwenden, ist der Test wirklich nicht zufällig .
Keine Antwort, aber um mehr Details zu diesem verwirrenden Problem bereitzustellen.
Leistungszähler zeigen ungefähr die gleiche Anzahl von Anweisungen, aber ungefähr die doppelte Anzahl von Zyklen, die verbraucht werden, wenn riesige Seiten verwendet werden:
- 4KiB Seiten IPC 0.29,
- 1-GiB-Seiten IPC 0.10.
Diese IPC-Zahlen besagen, dass der Code beim Speicherzugriff einen Engpass aufweist (CPU-gebundener IPC auf Skylake ist 3 und höher). Riesige Seiten Engpass härter.
Ich habe Ihren Benchmark so modifiziert, dass er MAP_POPULATE | MAP_LOCKED | MAP_FIXED
verwendet mit fester Adresse 0x600000000000
für beide Fälle, um Zeitvariationen zu eliminieren, die mit Seitenfehlern und zufälligen Zuordnungsadressen verbunden sind. Auf meinem Skylake-System sind 2 MiB und 1 GiB mehr als zweimal langsamer als 4 KB-Seiten.
Kompiliert mit 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
Läuft auf Ubuntu 18.04.5 LTS mit Intel i9-9900KS (das nicht NUMA ist), 4x8GiB 4GHz CL17 RAM in allen 4 Slots, mit performance
Governor für keine CPU-Frequenzskalierung, Flüssigkeitskühlungslüfter auf Maximum für keine thermische Drosselung, FIFO 40-Priorität für keine Vorrangigkeit, auf einem bestimmten CPU-Kern für keine CPU-Migration, mehrere Läufe. Die Ergebnisse sind ähnlich mit clang++-8.0.0
Compiler.
Es fühlt sich an, als wäre etwas in der Hardware faul, wie ein Speicherpuffer pro Seitenrahmen, sodass 4-KiB-Seiten ~ 2x mehr Speicher pro Zeiteinheit ermöglichen.
Wäre interessant, Ergebnisse für AMD Ryzen 3 CPUs zu sehen.
Auf AMD Ryzen 3 5950X ist die Huge-Pages-Version nur bis zu 10 % langsamer:
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