2012-06-30 5 views
13

Dopo che è stata rilasciata la funzione di calcolo 2.0 (Fermi), mi sono chiesto se per la memoria condivisa sono rimasti dei casi d'uso. Cioè, quando è meglio usare la memoria condivisa che lasciare che L1 esegua la sua magia sullo sfondo?CUDA: Quando utilizzare la memoria condivisa e quando fare affidamento sulla memorizzazione nella cache L1?

La memoria condivisa è semplicemente lì per consentire agli algoritmi progettati per CC < 2.0 di funzionare in modo efficiente senza modifiche?

Per collaborare tramite memoria condivisa, i thread in un blocco scrivono nella memoria condivisa e si sincronizzano con __syncthreads(). Perché non scrivere semplicemente nella memoria globale (tramite L1) e sincronizzarsi con __threadfence_block()? Quest'ultima opzione dovrebbe essere più semplice da implementare in quanto non deve riguardare due diverse posizioni di valori, e dovrebbe essere più veloce perché non esiste una copia esplicita dalla memoria globale a quella condivisa. Poiché i dati vengono memorizzati nella cache in L1, i thread non devono attendere che i dati raggiungano effettivamente la memoria globale.

Con la memoria condivisa, è garantito che un valore che è stato messo lì rimane lì per tutta la durata del blocco. Questo è in contrasto con i valori in L1, che vengono sfrattati se non vengono utilizzati abbastanza spesso. Ci sono casi in cui è meglio memorizzare nella cache i dati usati raramente nella memoria condivisa piuttosto che lasciare che la L1 li gestisca in base al modello di utilizzo effettivamente utilizzato dall'algoritmo?

risposta

6

Per quanto ne so, la cache L1 in una GPU si comporta come la cache di una CPU. Quindi il tuo commento che "Questo è in contrasto con i valori in L1, che vengono sfrattati se non vengono usati abbastanza spesso" non ha molto senso per me

I dati sulla cache L1 non sono sfrattati quando non è usato abbastanza spesso. Di solito è sfrattato quando viene fatta una richiesta per un'area di memoria che non era precedentemente nella cache e il cui indirizzo si risolve in uno già in uso. Non conosco l'algoritmo di caching esatto impiegato da NVidia, ma assumendo una normale associazione n-way, quindi ogni voce di memoria può essere memorizzata solo in un piccolo sottoinsieme dell'intera cache, basata sul suo indirizzo

Suppongo che questo può anche rispondere alla tua domanda. Con la memoria condivisa, hai il pieno controllo su ciò che viene memorizzato dove, mentre con la cache, tutto viene fatto automaticamente. Anche se il compilatore e la GPU possono ancora essere molto intelligenti nell'ottimizzare gli accessi alla memoria, a volte puoi ancora trovare un modo migliore, dato che sei quello che sa quale input sarà dato e quali thread faranno cosa (ad un certo portata ovviamente)

+1

Grazie, questo risponde alla mia domanda. Avevo immaginato la cache come in grado di tenere traccia di quali elementi venivano utilizzati di più, e preferisco memorizzarli nella cache. Ho letto su cache associative n-way ora e mi sembra che il problema principale è che possono buttare fuori un valore che viene spesso utilizzato semplicemente perché un'altra linea cache si adatta allo slot. –

+3

Penso che ciò significhi che una buona strategia per scrivere programmi CUDA potrebbe spesso essere prima scrivere l'algoritmo per usare solo la memoria globale e vedere se L1 funziona abbastanza bene che la latenza della memoria è nascosta. E poi considera l'ottimizzazione della mano con la memoria condivisa se l'algoritmo risulta vincolato alla memoria. –

7

I carichi/negozi di memoria globale sono soggetti alle regole di coalescenza, anche se i dati sono in cache, ma la memoria condivisa è molto più flessibile in termini di accesso casuale. Ho provato ad usare il caching L1 per memorizzare/calcolare un istogramma e finisce per essere molto, molto più lento di usare la memoria condivisa a causa del pattern di accesso semi-casuale.

Inoltre, in base a uno NVIDIA employee, le cache L1 correnti sono write-through (scrive immediatamente nella cache L2), il che rallenterà il programma.

Quindi, in pratica, le cache si intromettono se si desidera davvero prestazioni.

+1

Capacità di calcolo 2. * e 3.* invalidare la riga della cache L1 su write. Le capacità di calcolo 3.0-3.5 non memorizzano nella cache le letture globali in L1. Sulla capacità di calcolo 3. * dispositivi la larghezza di banda della memoria condivisa con 8 byte per banco è in realtà 256 byte/clk mentre L1 è limitata a 128 byte da una linea della cache. Come dichiarato da Yale, la memoria condivisa ha conflitti bancari (tutti gli accessi devono essere a banchi diversi o allo stesso indirizzo in una banca) mentre L1 ha divergenza nell'indirizzo (tutti gli indirizzi devono essere nella stessa riga della cache a 128 byte) quindi la memoria condivisa è molto più efficiente accesso casuale. –

+1

Lasciatemi offrire una congettura sul motivo per cui l'accesso alla memoria SIMD è praticamente inesistente sui processori generici (ad esempio, Intel AVX2 ha una raccolta, ma è davvero seriale). Sono abbastanza convinto che sia a causa del costo elevato delle traduzioni da virtuale a indirizzo fisico, che l'accesso alla memoria condivisa non ha bisogno perché è il suo spazio indirizzo. Immagina il costo di dover eseguire 32 ricerche TLB in parallelo! Forse c'è un'ottimizzazione se tutti e 32 gli indirizzi cadono nella stessa pagina? –