Considera il seguente programma per computer molto semplice:
for i = 1 to n:
y[i] = x[p[i]]
Qui ed y sono n matrici -element di byte, e p è un n matrice -element di parole. Qui n è grande, ad esempio n = 2 31 (in modo che solo una minima parte dei dati si adatti a qualsiasi tipo di memoria cache).
Supponiamo che costituito da numeri casuali , distribuiti uniformemente tra 1 e n .
Dal punto di vista dell'hardware moderno, ciò dovrebbe significare quanto segue:
- leggere è economico (lettura sequenziale)
- leggere è molto costoso (letture casuali; quasi tutte le letture sono mancate cache; dovremo recuperare ogni singolo byte dalla memoria principale)
- scrivere è economico (scrittura sequenziale).
E questo è davvero ciò che sto osservando. Il programma è molto lento rispetto a un programma che esegue solo letture e scritture sequenziali. Grande.
Ora arriva la domanda: in che misura questo programma si parallelizza sulle moderne piattaforme multi-core?
La mia ipotesi era che questo programma non si parallelizza bene. Dopotutto, il collo di bottiglia è la memoria principale. Un singolo core sta già perdendo la maggior parte del suo tempo in attesa di alcuni dati dalla memoria principale.
Tuttavia, questo non era quello che ho osservato quando ho iniziato a sperimentare alcuni algoritmi in cui il collo di bottiglia era questo tipo di operazione!
Ho semplicemente sostituito il for-loop ingenuo con un for-loop parallelo OpenMP (in sostanza, dividerà semplicemente l'intervallo in parti più piccole ed eseguirà queste parti su diversi core della CPU in parallelo).
Sui computer di fascia bassa, le accelerazioni erano davvero minori. Ma su piattaforme di fascia alta sono rimasto sorpreso dal fatto che stavo ottenendo eccellenti accelerazioni quasi lineari. Alcuni esempi concreti (i tempi esatti potrebbero essere un po 'fuori, ci sono molte variazioni casuali; questi erano solo esperimenti rapidi):
2 xeon a 4 core (in totale 8 core): fattore 5-8 accelerazioni rispetto alla versione a thread singolo.
Xeon 2 x 6 core (in totale 12 core): fattore di velocità 8-14 rispetto alla versione a thread singolo.
Questo era del tutto inaspettato. Domande:
Perché proprio questo tipo di programma si parallelizza così bene ? Cosa succede nell'hardware? (La mia ipotesi attuale è qualcosa del genere: le letture casuali da thread diversi sono "pipeline" e il tasso medio di ottenere risposte a questi è molto più alto che nel caso di un singolo thread.)
È necessario utilizzare più thread e più core per ottenere eventuali accelerazioni? Se un certo tipo di pipelining si verifica effettivamente nell'interfaccia tra la memoria principale e la CPU, un'applicazione a thread singolo non potrebbe far sapere alla memoria principale che presto avrà bisogno di , x [ p [ i + 1 ] ] , ... e il computer potrebbe iniziare a recuperare le relative linee di cache dalla memoria principale? Se ciò è possibile in linea di principio, come posso realizzarlo in pratica?
Qual è il modello teorico giusto che potremmo usare per analizzare questo tipo di programmi (e fare previsioni corrette delle prestazioni)?
Modifica: ora sono disponibili alcuni codici sorgente e risultati di benchmark disponibili qui: https://github.com/suomela/parallel-random-read
Alcuni esempi di figure del campo da baseball ( ):
- circa. 42 ns per iterazione (lettura casuale) con un singolo thread
- circa. 5 ns per iterazione (lettura casuale) con 12 core.