Sembra che tu voglia un modo per valutare quanto è legato il tuo codice FPU o quanto efficacemente stai usando la FPU, piuttosto che contare il numero di flop secondo la stessa definizione anacronistica di un "flop". In altre parole, si desidera una metrica che raggiunga lo stesso picco se ogni unità a virgola mobile funziona a piena capacità ogni ciclo. Diamo un'occhiata a un Intel Sandy Bridge per vedere come questo potrebbe sconvolgere.
Operazioni in virgola mobile supportate dall'hardware
Questo chip supporta le istruzioni AVX , quindi i registri sono lunghi 32 byte (con 4 doppie). L'architettura superscalare consente alle istruzioni di sovrapporsi, con la maggior parte delle istruzioni aritmetiche che richiedono alcuni cicli per completare, anche se una nuova istruzione potrebbe essere in grado di iniziare il ciclo successivo. Queste semantiche sono solitamente abbreviate scrivendo latenza / throughput inverso, un valore di 5/2 significherebbe che il completamento dell'istruzione richiede 5 cicli, ma è possibile avviare una nuova istruzione ogni altro ciclo (supponendo che gli operandi siano disponibili, quindi nessun dato dipendenza e non aspettare memoria).
Esistono tre unità aritmetiche in virgola mobile per core, ma la terza non è rilevante per la nostra discussione, chiameremo le due unità A e M rilevanti perché le loro funzioni primarie sono addizione e moltiplicazione. Istruzioni di esempio (vedere le tabelle della nebbia di Agner )
vaddpd
: aggiunta imballata, unità occupante A per 1 ciclo, latenza / capacità inversa è 3/1
vmulpd
: moltiplicazione imballata, unità M, 5/1
vmaxpd
: imballato selezionare massimo a coppie, unità A, 3/1
vdivpd
: divisione divisa, unità M (e qualche A), da 21/20 a 45/44 a seconda dell'ingresso
vsqrtpd
: radice quadrata impaccata, alcune A e M, dal 21/21 al 43/43 a seconda dell'ingresso
vrsqrtps
: radice quadrata reciproca a bassa precisione compatta per input a precisione singola (8 floats
)
La semantica precisa per ciò che può sovrapporsi vdivpd
e vsqrtpd
apparentemente sottile e AFAIK, non documentata da nessuna parte. Nella maggior parte degli usi, penso che ci siano poche possibilità di sovrapposizione, sebbene la formulazione nel manuale suggerisca che più thread possono offrire più possibilità di sovrapposizione in questa istruzione. Siamo in grado di raggiungere picchi di picco se iniziamo un vaddpd
e vmulpd
su ogni ciclo, per un totale di 8 flop per ciclo. Il multiplo denso matrice-matrice ( dgemm
) può avvicinarsi ragionevolmente a questo picco.
Quando si contano i flop per istruzioni speciali, vorrei vedere quanta parte della FPU è occupata. Supponiamo che nel tuo intervallo di input siano stati vdivpd
necessari in media 24 cicli per completare, occupando completamente l'unità M, ma l'addizione potrebbe (se fosse disponibile) essere eseguita contemporaneamente per metà dei cicli. L'FPU è in grado di eseguire 24 moltiplicazioni impacchettate e 24 aggiunte impacchettate durante quei cicli (perfettamente intercalati vaddpd
e vmulpd
), ma con un vdivpd
, il meglio che possiamo fare è 12 aggiunte impacchettate aggiuntive. Se supponiamo che il modo migliore per fare divisione sia usare l'hardware (ragionevole), potremmo contare i vdivpd
36 "flop" compressi, indicando che dovremmo contare ogni divisione scalare come 36 "flop".
Con la radice quadrata reciproca, a volte è possibile battere l'hardware, soprattutto se non è necessaria la massima precisione o se l'intervallo di input è limitato. Come accennato in precedenza, l' vrsqrtps
istruzione è molto economica, quindi (se in una sola precisione) potresti fare una vrsqrtps
seguita da una o due iterazioni di Newton per ripulire. Queste iterazioni di Newton sono giuste
y *= (3 - x*y*y)*0.5;
Se è necessario eseguire molte di queste operazioni, ciò può essere significativamente più veloce della valutazione ingenua di y = 1/sqrt(x)
. Prima della disponibilità di radice quadrata reciproca approssimativa approssimativa dell'hardware, alcuni codici sensibili alle prestazioni utilizzavano operazioni di numeri interi famigerati per trovare un'ipotesi iniziale per l'iterazione di Newton.
Funzioni matematiche fornite dalla libreria
Possiamo applicare un'euristica simile alle funzioni matematiche fornite dalla biblioteca. Puoi creare un profilo per determinare il numero di istruzioni SSE, ma come abbiamo discusso, questa non è l'intera storia e un programma che passa tutto il suo tempo a valutare funzioni speciali potrebbe non sembrare avvicinarsi al picco, il che potrebbe essere vero, ma non è vero è utile per dirti che tutto il tempo è trascorso fuori dal tuo controllo sulla FPU.
Suggerisco di utilizzare una buona libreria matematica vettoriale come base (ad esempio VML di Intel, parte di MKL). Misura il numero di cicli per ogni chiamata e moltiplica per i flop di picco ottenibili su quel numero di cicli. Quindi, se un esponenziale impacchettato impiega 50 cicli per essere valutato, contalo come 100 flop per la larghezza del registro. Sfortunatamente, le librerie matematiche vettoriali a volte sono difficili da chiamare e non hanno tutte le funzioni speciali, quindi potresti finire per fare matematica scalare, nel qual caso valuteresti il nostro ipotetico esponenziale scalare come 100 flop (anche se probabilmente ci vogliono ancora 50 cicli, quindi otterresti solo il 25% di "picco" se tutto il tempo viene impiegato per valutare questi esponenziali).
Come altri hanno già detto, è possibile contare cicli e contatori di eventi hardware utilizzando PAPI o varie interfacce. Per un semplice conteggio dei cicli, è possibile leggere direttamente il contatore dei cicli utilizzando le rdtsc
istruzioni con uno snippet di assembly inline.