Intel è stata così gentile da rispondere a questo problema. Vedi la loro risposta qui sotto.
Questo problema è dovuto al modo in cui le pagine fisiche vengono effettivamente impegnate. Nel caso di pagine da 1GB, la memoria è contigua. Pertanto, non appena si scrive su un byte qualsiasi all'interno della pagina da 1 GB, viene assegnata l'intera pagina da 1 GB. Tuttavia, con le pagine da 4 KB, le pagine fisiche vengono assegnate man mano che tocchi per la prima volta ciascuna delle pagine da 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;
}
}
Nel ciclo più interno, l'indice cambia a un passo di 512 KB. Quindi, i riferimenti consecutivi vengono mappati con offset di 512 KB. In genere le cache hanno 2048 set (che è 2 ^ 11). Quindi, i bit 6:16 selezionano i set. Ma se procedi con offset di 512 KB, i bit 6:16 sarebbero gli stessi, finendo per selezionare lo stesso set e perdere la località spaziale.
Consigliamo di inizializzare l'intero buffer da 1 GB in sequenza (nel test della pagina piccola) come di seguito prima di avviare l'orologio per cronometrarlo
for (uint64_t i = 0; i < size / sizeof(*ptr); i++)
ptr[i] = i * 5;
Fondamentalmente, il problema riguarda i conflitti tra insiemi che risultano in cache miss in caso di pagine enormi rispetto a pagine piccole a causa di offset costanti molto grandi. Quando usi offset costanti, il test non è davvero casuale .
Non una risposta, ma per fornire maggiori dettagli a questo problema sconcertante.
I contatori delle prestazioni mostrano un numero di istruzioni approssimativamente simile, ma circa il doppio del numero di cicli spesi quando vengono utilizzate pagine enormi:
- Pagine da 4 KiB IPC 0,29,
- Pagine da 1GiB IPC 0.10.
Questi numeri IPC indicano che il codice è bloccato sull'accesso alla memoria (l'IPC associato alla CPU su Skylake è 3 e superiore). Il collo di bottiglia di pagine enormi è più difficile.
Ho modificato il tuo benchmark per usare MAP_POPULATE | MAP_LOCKED | MAP_FIXED
con indirizzo fisso 0x600000000000
in entrambi i casi per eliminare la variazione temporale associata a errori di pagina e indirizzo di mappatura casuale. Sul mio sistema Skylake 2MiB e 1GiB sono più di 2 volte più lenti delle pagine da 4kiB.
Compilato 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
Girato su Ubuntu 18.04.5 LTS con Intel i9-9900KS (che non è NUMA), 4x8GiB 4GHz CL17 RAM in tutti e 4 gli slot, con performance
regolatore per nessuna scalabilità della frequenza della CPU, ventole di raffreddamento a liquido al massimo per nessuna limitazione termica, priorità FIFO 40 per nessuna prelazione, su un core della CPU specifico per nessuna migrazione della CPU, esecuzioni multiple. I risultati sono simili con clang++-8.0.0
compilatore.
Sembra che ci sia qualcosa di sospetto nell'hardware, come un buffer di negozio per frame di pagina, in modo che le pagine da 4 KiB consentano ~2 volte più negozi per unità di tempo.
Sarebbe interessante vedere i risultati per le CPU AMD Ryzen 3.
Su AMD Ryzen 3 5950X la versione con pagine enormi è solo fino al 10% più 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