Domanda

Questo fa parte dell'output di cachegrind.Questa parte di codice è stata eseguita 1224 volte.elmg1 è un array di unsigned long di dimensione 16 x 20.La dimensione della cache L1 della mia macchina è di 32 KB, dimensione della linea di cache di 64 B e set associativo a 8 vie.

  1. per (i = 0;io < 20;i++) 78.336 2.448 2 50.184 0 0 1.224 0 0
  2. {
  3. telm01 = elmg1[i];146.880 0 0 73.440 0 0 24.480 0 0
  4. telm31 = (telm01 << 3) ^ val1;97.920 0 0 48.960 0 0 24.480 0 0
  5. telm21 = (telm01 << 2) ^ (val1 >> 1);146.880 1.224 1 48.960 0 0 24.480 0 0
  6. telm11 = (telm01 << 1) ^ (val1 >> 2);146.880 0 0 48.960 0 0 24.480 0 0
  7. }

UN.Il motivo per cui l'ho inserito qui è che nella terza riga all'interno del ciclo for vedo un numero di errori I1 (anche un errore L2).È un po 'confuso e non riesco a indovinare il motivo?

B.Sto cercando di ottimizzare (tempo) una porzione di codice.Quanto sopra è solo un piccolo frammento.Penso che l'accesso alla memoria del mio programma mi costi molto.Come nell'esempio sopra, elmg1 è un array di dimensioni 16 x 20 di long senza segno.Quando provo a usarlo nel codice, ci sono sempre degli errori e nel mio programma queste variabili si verificano spesso.Eventuali suggerimenti?

C.Devo allocare e (a volte inizializzare) questi long senza segno.Potete suggerire quale dovrei preferire, calloc o dichiarazione di array e quindi inizializzazione esplicita.A proposito, ci sarà qualche differenza nel modo in cui la cache li gestisce?

Grazie.

È stato utile?

Soluzione

Hai provato a srotolare il loop?

  1. Non mi preoccuperei dei fallimenti L1 in questo momento.Anche un errore L2 su 1224 volte è ok, la CPU deve caricare i valori nella cache ad un certo punto.
  2. Quale percentuale di errori L2 costa questo codice rispetto al resto del programma?
  3. Usa calloc(), se la dimensione dell'array è sempre la stessa e usi costanti per la dimensione, il compilatore può ottimizzare l'azzeramento dell'array.Inoltre, l'unica cosa che influenzerebbe l'utilizzo delle linee della cache è l'allineamento, non il modo in cui è stato inizializzato.

modificare:Il numero era difficile da leggere in quel modo e leggerlo male la prima volta.

assicuriamoci di leggere correttamente i numeri per la riga 5:

Ir    146,880
I1mr  1,224
ILmr  1
Dr    48,960
D1mr  0
DLmr  0
Dw    24,480
D1mw  0
DLmw  0

La cache L1 è divisa in due cache da 32KByte una per il codice I1 e una per i dati D1.IL e DL sono la cache L2 o L3 condivisa sia dai dati che dalle istruzioni.

Il gran numero di I1mr sono istruzioni mancate e non dati mancati, ciò significa che il codice del loop viene espulso dalla cache delle istruzioni I1.

I1 manca alla riga 1 e 5 in totale 3672 che è 3 volte 1224, quindi ogni volta che viene eseguito il ciclo si ottengono 3 mancati cache I1 con righe di cache da 64 byte, il che significa che la dimensione del codice del loop è compresa tra 128 e 192 byte per coprire 3 righe di cache.Quindi quei mancati I1 alla riga 5 sono perché è lì che il codice del loop attraversa l'ultima riga della cache.

Consiglierei di utilizzare KCachegrind per visualizzare i risultati di cachegrind

Modificare:Maggiori informazioni sulle linee di cache.

Quel codice del loop non sembra essere chiamato 1224 volte da solo, quindi significa che c'è più codice che sta spingendo questo codice fuori dalla cache I1.

La tua cache I1 da 32 Kbyte è divisa in 512 linee di cache (64 byte ciascuna).La parte "8-way set associative" significa che ciascun indirizzo di memoria è mappato solo su 8 delle 512 linee di cache.Se l'intero programma che stai profilando fosse un blocco continuo di 32 KB di memoria, allora entrerebbe tutto nella cache I1 e nessuno verrebbe espulso.Molto probabilmente non è così e ci saranno più di 8 blocchi di codice da 64 byte contenuti per le stesse 8 righe di cache.Diciamo che l'intero programma ha 1 Mbyte di codice (questo include le librerie), quindi ogni gruppo di 8 righe di cache avrà circa 32 (1 Mbyte/32 Kbyte) pezzi di codice contenuti per quelle stesse 8 righe di cache.

Leggi questo articolo di lwn.net per tutti i dettagli cruenti sulle cache della CPU

Il compilatore non è sempre in grado di rilevare quali funzioni del programma saranno hotspot (chiamate molte volte) e quali saranno codespot (cioècodice del gestore errori, che non viene quasi mai eseguito).GCC ha attributi di funzione caldo freddo che ti consentirà di contrassegnare le funzioni come calde/fredde, ciò consentirà al compilatore di raggruppare insieme le funzioni calde in un blocco di memoria per ottenere un migliore utilizzo della cache (ad es.il codice freddo non spingerà il codice caldo fuori dalle cache).

Ad ogni modo, non vale davvero la pena preoccuparsi di quei fallimenti I1.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top