Quindi mi sono un po 'incuriosito e ho fatto qualche ricerca. Principalmente per vedere quanto sia più utile il framework perf ora dall'ultima volta che l'ho usato, ha fatto crash del kernel su una macchina condivisa e altri 25 sviluppatori erano abbastanza insoddisfatti dei miei esperimenti.
prima cosa, verificare che vedo quello che si vede:
$ cc -O -o xx xx.c && perf stat -e L1-dcache-loads,L1-dcache-stores ./xx
Performance counter stats for './xx':
58,764,160 L1-dcache-loads
81,640,635 L1-dcache-stores
Yup. Numeri ancora più grandi. Quindi che sta succedendo? Diamo registrare e analizzare questo un po 'meglio:
$ cc -O -o xx xx.c && perf record -e L1-dcache-loads,L1-dcache-stores ./xx
[... blah blah ...]
$ perf report --stdio
[... blah blah ...]
# Samples: 688 of event 'L1-dcache-loads'
# Event count (approx.): 56960661
#
# Overhead Command Shared Object Symbol
# ........ ....... ................. ........
#
95.80% xx [kernel.kallsyms] [k] 0xffffffff811176ee
4.20% xx xx [.] main
# Samples: 656 of event 'L1-dcache-stores'
# Event count (approx.): 80623804
#
# Overhead Command Shared Object Symbol
# ........ ....... ................. ........
#
61.72% xx [kernel.kallsyms] [k] 0xffffffff811176ee
38.28% xx xx [.] main
Aha, in modo che il kernel è responsabile per la maggior parte di quei carichi e negozi. I contatori di cui teniamo conto sono gli accessi alla cache che fanno sia il kernel che l'utente.
Che cosa sta succedendo è che le pagine fisiche del programma (compresi il segmento di dati e bss) non sono mappate o addirittura assegnate quando si avvia il programma. Il kernel li disturba quando li tocchi per la prima volta (o in futuro se sono stati eliminati). Possiamo vedere questo con:
$ cc -O -o foo foo.c && perf stat -e faults ./xx
Performance counter stats for './xx':
58,696 faults
In realtà stiamo eseguendo errori di pagina 58.7k solo durante quella corsa. Dato che la dimensione della pagina è 4096 byte otteniamo 58696*4096=240418816
che è approssimativamente il 240000000 byte per gli array e il resto è il programma, lo stack e tutti i tipi di posta indesiderata in libc e ld.so richiesti per l'esecuzione.
Quindi ora possiamo capire i numeri. Diamo prima un'occhiata ai negozi, perché dovrebbero essere i più facili da capire. 80623804*0.3828=30862792.1712
, quindi ha senso. Ci aspettavamo 30 milioni di negozi e abbiamo ottenuto il 30,9. Dal momento che i contatori delle prestazioni si campionano e non sono perfettamente accurati, ciò è previsto. Alcuni dei carichi che il kernel ha riversato e sono stati resi conto del programma.In altre esecuzioni ho ottenuto meno di 30 milioni di conteggi per l'area utente.
Allo stesso modo in userland vengono caricati 2,4 M. Sospetto che quelli non siano effettivamente carichi in userland ma per qualche motivo alcuni accessi che il kernel fa quando ritornano dalle trappole che vengono contabilizzate nel tuo programma. O qualcosa di simile. Non ne sono sicuro, non mi piacciono, ma vediamo se possiamo rimuovere quel rumore e controllare la teoria che ha qualcosa a che fare con i dati inutili causati da errori di pagina.
Ecco una versione aggiornata del test:
#include <stdio.h>
#define STREAM_ARRAY_SIZE 10000000
static double a[STREAM_ARRAY_SIZE],
b[STREAM_ARRAY_SIZE],
c[STREAM_ARRAY_SIZE];
void
setup(void)
{
memset(a, 0, sizeof a);
memset(b, 0, sizeof b);
memset(c, 0, sizeof c);
}
void
bench(void)
{
ssize_t j;
for (j = 0; j < STREAM_ARRAY_SIZE; j++) {
a[j] = 1.0;
b[j] = 2.0;
c[j] = 0.0;
}
}
int
main(int argc, char **argv)
{
setup();
bench();
return 0;
}
faccio in modo di ottenere tutti i page fault durante setup
e poi tutti i contatori che si rovesciano durante bench
dovrebbero avere molto poco rumore del kernel in loro.
$ cc -O -o xx xx.c && perf record -e faults,L1-dcache-loads,L1-dcache-stores ./xx
[...]
$ perf report --stdio
[...]
# Samples: 468 of event 'faults'
# Event count (approx.): 58768
#
# Overhead Command Shared Object Symbol
# ........ ....... ................. .................
#
99.20% xx libc-2.12.so [.] __memset_sse2
0.69% xx ld-2.12.so [.] do_lookup_x
0.08% xx ld-2.12.so [.] dl_main
0.02% xx ld-2.12.so [.] _dl_start
0.01% xx ld-2.12.so [.] _start
0.01% xx [kernel.kallsyms] [k] 0xffffffff8128f75f
# Samples: 770 of event 'L1-dcache-loads'
# Event count (approx.): 61518838
#
# Overhead Command Shared Object Symbol
# ........ ....... ................. .................
#
96.14% xx [kernel.kallsyms] [k] 0xffffffff811176ee
3.86% xx libc-2.12.so [.] __memset_sse2
# Samples: 866 of event 'L1-dcache-stores'
# Event count (approx.): 98243116
#
# Overhead Command Shared Object Symbol
# ........ ....... ................. .................
#
53.69% xx [kernel.kallsyms] [k] 0xffffffff811176ee
30.62% xx xx [.] bench
15.69% xx libc-2.12.so [.] __memset_sse2
E ce l'hai. Gli errori di pagina si sono verificati durante la chiamata a memset
e alcuni durante il collegamento dinamico, il rumore che era in precedenza in main ora si verifica durante memset
, bench
stesso non ha carichi e circa 30 milioni di negozi. Proprio come ci aspettavamo. Una nota divertente qui è che memset
sa come essere efficiente su questa macchina e ha fatto solo metà degli archivi rispetto al test per riempire la stessa quantità di memoria. "Sse2" in __memset_sse2
è un buon suggerimento su come.
Ho appena realizzato che una cosa potrebbe non essere chiara e non so dove metterla, quindi la lascio qui. I contatori delle prestazioni contano accuratamente gli eventi, ma per quanto ne so, se vuoi sapere dove avvengono quegli eventi, la CPU può generare una trappola solo una volta ogni X eventi registrati. Quindi gli strumenti non sanno esattamente dove avvengono gli eventi (sarebbe troppo lento per funzionare in quel modo), invece aspettiamo che arrivi la trappola e rendiamo conto di tutti gli eventi X a quell'istruzione/funzione. Penso, ma non sono sicuro che X sia almeno 10000. Quindi se la funzione bench
tocca lo stack una sola volta e ciò accade per generare il trap Lill-dcache-load spill, dovrai considerare 10000 letture a quella lettura di lo stack. Inoltre, per quanto ne so, i fallimenti di TLB (di cui si otterrà circa 58593) nella funzione bench
vengono risolti anche attraverso la cache L1 e verranno considerati a tale scopo. Quindi, qualunque cosa tu faccia, non otterrai mai esattamente i numeri che ti aspetti qui.
Hai provato a ridimensionare le dimensioni della matrice e controllare come influisce sui risultati? Se non cambia, potrebbe essere solo un overhead non correlato – Leeor
Il rapporto tra i carichi e lo store rimane lo stesso, quindi mi manca sicuramente qualcosa su come riconciliare i mov con carichi e negozi. – igon
Come punto dati, sul mio AMD FX8350, i valori sono nell'ordine dei carichi di '10 * STREAM_ARRAY_SIZE', e solo gli archivi' STREAM_ARRAY_SIZE' (ovvero un terzo del previsto). Questo è ancora più strano :) – Jester