HPC - Prodotto scalare SIMD

Moreno Marzolla moreno.marzolla@unibo.it

Ultimo aggiornamento: 2021-05-16

Verifica dell'ambiente

Verificare quali estensioni SIMD sono supportate dalla CPU esaminando l'output del comando cat /proc/cpuinfo oppure del comando lscpu. Cercare nel campo flags la presenza delle sigle mmx, sse, sse2, sse3, sse4_1, sse4_2, avx, avx2.

Si suggerisce di compilare i programmi SIMD come:

    gcc -std=c99 -Wall -Wpedantic -O2 -march=native -g -ggdb prog.c -o prog

dove:

Può essere utile analizzare il codice assembly prodotto dal compilatore, ad esempio per vedere se vengono effettivamente usate istruzioni SIMD. Per fare ciò è sufficiente dare in comando:

objdump -dS nome_eseguibile

Per sapere quali flag del compilatore sono attivi con -march=native si usa il comando:

gcc -march=native -Q --help=target

Prodotto scalare

Il file simd-dot.c contiene una funzione che calcola il prodotto scalare tra due array di float. Scopo di questo esercizio è di implementare la funzione simd_dot() per il calcolo del prodotto scalare usando parallelismo SIMD, procedendo secondo i passi descritti sotto.

Il programma stampa il tempo medio di esecuzione della versione seriale e parallela della funzione di calcolo, ottenuto ripetendo il calcolo un certo numero di volte. Il calcolo del prodotto scalare è una procedura piuttosto semplice che richiede pochissimo tempo anche con array di grandi dimensioni. Di conseguenza, potrebbero non apparire differenze significative tra le prestazioni della versione SIMD e di quella scalare.

1. Auto-vettorizzazione. Verificare l'efficacia del compilatore nell'auto-vettorizzazione della funzione scalar_dot(). Si compili il programma con il seguente comando:

    gcc -O2 -march=native -ftree-vectorize -fopt-info-vec-optimized \
    -fopt-info-vec-missed simd-dot.c -o simd-dot -lm 2>&1 | \
    grep "loop vectorized"

I flag -fopt-info-vec-XXX stampano una serie di messaggi "informativi" (per modo di dire) sullo standard error indicando quali cicli sono stati vettorizzati e quali no. La riga di comando redireziona lo standard error sullo standard output e cerca la stringa loop vectorized che viene stampata dal compilatore quando riesce a vettorizzare un ciclo.

Dovrebbe venire stampato un solo messaggio: il compilatore riesce a vettorizzare il ciclo della funzione fill(), ma non quello della funzione serial_dot().

2. Auto-vettorizzazione (secondo tentativo). Esaminare i messaggi di output del compilatore (rimuovere da 2>&1 in poi dalla riga di comando precedente); dovrebbe essere presente un messaggio simile al seguente:

    simd-dot.c:168:5: note: reduction: unsafe fp math optimization: r_17 = _9 + r_20;

La riga 168 (nella mia versione del sorgente) è il ciclo "for" della funzione scalar_dot(); si tratta dello stesso messaggio di cui abbiamo parlato a lezione, e indica che le istruzioni:

r += x[i] * y[i];

sono parte di una operazione di riduzione che coinvolge operandi di tipo float. Poiché l'aritmetica in virgola mobile non gode della proprietà commutativa, il compilatore non applica la vettorizzazione per non alterare l'ordine delle somme. Per ignorare il problema si ricompili il programma con il flag -funsafe-math-optimizations:

    gcc -O2 -march=native -ftree-vectorize -fopt-info-vec-optimized \
    -fopt-info-vec-missed -funsafe-math-optimizations simd-dot.c \
    -o simd-dot -lm  2>&1 | grep "loop vectorized"

Dovrebbe ora comparire (due volte) il messaggio

    simd-dot.c:168:5: note: loop vectorized

che indica che il ciclo è stato vettorizzato.

3. Vettorizzare manualmente il codice. Si realizzi la funzione simd_dot() usando i vector datatype del compilatore GCC. La funzione sarà quasi identica alla funzione per il calcolo della somma del contenuto di un array vista a lezione (si faccia riferimento al programma simd-vsum-vector.c nell'archivio degli esempi). La funzione simd_dot() realizzata deve funzionare correttamente per qualsiasi lunghezza \(n\) degli array di input, che non deve quindi essere multipla dell'ampiezza dei registri SIMD; si garantisce però che gli array di input siano sempre correttamente allineati.

Compilare con:

    gcc -std=c99 -Wall -Wpedantic -O2 -march=native -g -ggdb simd-dot.c -o simd-dot -lm

Eseguire con:

    ./simd-dot [n]

Esempio:

    ./simd-dot 20000000

File