Conteggio FLOP per le funzioni di libreria


13

Quando si valuta il numero di FLOP in una semplice funzione, spesso si può semplicemente abbassare l'espressione calcolando gli operatori aritmetici di base. Tuttavia, nel caso di affermazioni matematiche che coinvolgono una divisione uniforme, non si può fare questo e aspettarsi di essere in grado di confrontare con i conteggi FLOP da funzioni con solo aggiunte e moltiplicazioni. La situazione è ancora peggiore quando l'operazione è implementata in una libreria. Pertanto, è indispensabile avere una nozione ragionevole dell'esecuzione delle funzioni speciali.

Per funzioni speciali, intendiamo cose come:

  • exp ()
  • sqrt ()
  • sin / cos / tan ()

che sono generalmente forniti dalle librerie di sistema.

Determinare la complessità di questi è ulteriormente confuso dal fatto che molti di loro sono adattativi e hanno complessità dipendente dall'input. Ad esempio, implementazioni numericamente stabili di exp () spesso ridimensionano e usano adattivamente le ricerche. La mia prima impressione qui è che in questo caso il migliore che si possa fare è accertare il comportamento medio delle funzioni.

L'intera discussione è, ovviamente, fortemente dipendente dall'architettura. Per questa discussione possiamo limitarci alle architetture tradizionali per scopi generali ed escludere quelle con unità funzionali speciali (GPU, ecc.)

Si possono trovare tentativi abbastanza semplici di standardizzare questi per architetture particolari per il confronto tra sistema e sistema, ma questo non è accettabile se ci si preoccupa delle prestazioni tra metodo e metodo. Quali metodologie per determinare la complessità FLOP di queste funzioni sono considerate accettabili? Ci sono delle insidie ​​importanti?


Peter, solo un breve commento. Sebbene forniate diversi buoni esempi di funzioni fornite dalle librerie matematiche, le divisioni in virgola mobile sono normalmente implementate dall'unità in virgola mobile.
Aron Ahmadia,

Grazie! Non ero abbastanza chiaro. Ho appena modificato per offrire un migliore contrasto.
Peter Brune,

Sono stato sorpreso di scoprire che sin, cos e sqrt sono tutti effettivamente implementati nel sottoinsieme a virgola mobile x87 delle istruzioni x86. Penso di avere il tuo punto, ma penso che la pratica accettata sia solo quella di trattarli come operazioni in virgola mobile con costanti leggermente più grandi :)
Aron Ahmadia,

@AronAhmadia Non c'è stato motivo di usare x87 in oltre un decennio. Dividi e sqrt()sono in SSE / AVX, ma richiedono molto più tempo di addizioni e multilicazioni. Inoltre, sono scarsamente vettorializzati su Sandy Bridge AVX, impiegando il doppio delle istruzioni SSE (con metà larghezza). Ad esempio, AVX a doppia precisione (4 doppie di larghezza) può eseguire una moltiplicazione e aggiungere pacchetti ogni ciclo (supponendo che non vi siano dipendenze o blocchi sulla memoria) che è di 8 flop per ciclo. La divisione richiede tra 20 e 44 cicli per fare quei "4 flop".
Jed Brown,

sqrt () è facoltativo su PowerPC. Molti chip integrati di questa architettura non implementano le istruzioni, ad esempio la serie Freescale MPC5xxx.
Damien,

Risposte:


10

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 vdivpde vsqrtpdapparentemente 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 vaddpde vmulpdsu 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 vdivpdnecessari 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 vaddpde 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 vdivpd36 "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' vrsqrtpsistruzione è molto economica, quindi (se in una sola precisione) potresti fare una vrsqrtpsseguita 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 rdtscistruzioni con uno snippet di assembly inline.


7

Potresti contarli su sistemi reali usando PAPI , che consente l'accesso a contatori hardware e semplici programmi di test. La mia interfaccia / wrapper PAPI preferita è IPM (Integrated Performance Monitor) ma esistono altre soluzioni ( TAU , ad esempio). Ciò dovrebbe fornire un confronto metodo-metodo abbastanza stabile.


4

Risponderò a questa domanda come se tu mi chiedessi:

"Come posso confrontare analiticamente o prevedere le prestazioni di algoritmi che si basano fortemente su funzioni speciali, invece dei tradizionali conteggi FLOP moltiplicare-aggiungere-trasportare che provengono dall'algebra lineare numerica"

Concordo con la tua prima premessa, che le prestazioni di molte funzioni speciali dipendono dall'architettura e che, sebbene di solito tu possa considerare ciascuna di queste funzioni come un costo costante, la dimensione della costante varierà, anche tra due processori dalla stessa società ma con architetture diverse (vedere la tabella dei tempi delle istruzioni di Agner Fog per riferimento).

Non sono d'accordo, tuttavia, che il focus del confronto dovrebbe essere sui costi delle singole operazioni in virgola mobile. Penso che il conteggio dei FLOP sia ancora in qualche modo utile, ma che ci sono molte considerazioni molto più importanti che possono rendere meno rilevanti i costi delle funzioni speciali quando si confrontano due potenziali algoritmi, e questi dovrebbero essere esplicitamente esaminati prima di passare a un confronto di operazioni in virgola mobile:

  1. Scalabilità - Gli algoritmi che presentano compiti che possono essere implementati in modo efficiente su architetture parallele domineranno l'arena del calcolo scientifico per il prossimo futuro. Un algoritmo con una migliore "scalabilità", sia attraverso una comunicazione più bassa, meno necessità di sincronizzazione o un migliore bilanciamento del carico naturale, può impiegare funzioni speciali più lente e quindi essere più lento per un numero limitato di processi, ma alla fine raggiungerà il numero dei processori è aumentato.

  2. Località temporale di riferimento: l'algoritmo riutilizza i dati tra le attività, consentendo al processore di evitare il traffico di memoria non necessario? Ogni livello della gerarchia di memoria che un algoritmo attraversa aggiunge un altro ordine di costo di grandezza (approssimativamente) a ciascun accesso alla memoria. Di conseguenza, un algoritmo con un'alta densità di operazioni speciali sarà probabilmente molto più veloce di un algoritmo con il numero equivalente di semplici operazioni di funzione su una più ampia regione di memoria.

  3. Impronta di memoria - Questo è fortemente correlato ai punti precedenti, ma man mano che i computer diventano sempre più grandi, la quantità di memoria per core tende effettivamente verso il basso. Ci sono due vantaggi in un footprint di memoria ridotto. Il primo è che una piccola quantità di dati del programma sarà probabilmente in grado di adattarsi completamente alla cache del processore. Il secondo è che, per problemi molto grandi, un algoritmo con un footprint di memoria più piccolo potrebbe essere in grado di adattarsi alla memoria del processore, consentendo di risolvere i problemi che altrimenti supererebbero la capacità del computer.


Direi che conoscere FLOPS / sec ti consente di separare in quale regime di collo di bottiglia (memoria, comunicazione) sei abbastanza bene. Ad esempio, considera i metodi di Newton-Krylov, che trascorrono molto del loro tempo a fare matvec. Matvecs fa un FLOP o due per ogni voce della matrice e il gioco è fatto. Gli smoothers non assemblati hanno il potenziale per fare meglio. Ne abbiamo parlato anche io e Jed, e un'idea alternativa è vedere quanti cicli stai spendendo nel calcolo associato a FLOP. Tuttavia, ciò può richiedere un monitoraggio abbastanza accurato e FLOPS / sec totali potrebbero essere più pratici.
Peter Brune,

Aron, la maggior parte di questa risposta sembra eludere la domanda di Peter a favore di rispondere a questa altra domanda: scicomp.stackexchange.com/questions/114
Jed Brown

@JedBrown, sono d'accordo, grazie per aver dedicato del tempo a mettere insieme una risposta molto più solida.
Aron Ahmadia,

0

Perché preoccuparsi di contare i flop? Basta contare i cicli per ogni operazione e avrai qualcosa di universale.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.