Sto cercando alcuni consigli su come eseguire una somma di prefisso parallelo con SSE. Sono interessato a farlo su una schiera di Ints, Floating o Double.somma cumulativa di prefisso (cumulativo) con SSE
Ho trovato due soluzioni. Un caso speciale e un caso generale. In entrambi i casi la soluzione viene eseguita sull'array in due passaggi in parallelo con OpenMP. Per il caso speciale uso SSE su entrambe le passate. Per il caso generale lo uso solo al secondo passaggio.
La mia domanda principale è come utilizzare SSE al primo passaggio nel caso generale? Il seguente collegamento simd-prefix-sum-on-intel-cpu mostra un miglioramento per i byte ma non per i tipi di dati a 32 bit.
Il motivo per cui il caso speciale è chiamato speciale è che richiede che l'array sia in un formato speciale. Ad esempio, supponiamo che esistano solo 16 elementi di un array a
di float. Poi, se la matrice fu rinviato simili (array di struct a struct di array):
a[0] a[1] ...a[15] -> a[0] a[4] a[8] a[12] a[1] a[5] a[9] a[13]...a[3] a[7] a[11] a[15]
SSE somme verticali possono essere usati in entrambi i passaggi. Tuttavia, ciò sarebbe efficace solo se gli array fossero già nel formato speciale e l'output potesse essere utilizzato nel formato speciale. In caso contrario, sarebbe necessario effettuare un riorganizzazione costosa su input e output, il che renderebbe molto più lento rispetto al caso generale.
Forse dovrei considerare un algoritmo diverso per la somma del prefisso (ad esempio un albero binario)?
Codice per il caso generale:
void prefix_sum_omp_sse(double a[], double s[], int n) {
double *suma;
#pragma omp parallel
{
const int ithread = omp_get_thread_num();
const int nthreads = omp_get_num_threads();
#pragma omp single
{
suma = new double[nthreads + 1];
suma[0] = 0;
}
double sum = 0;
#pragma omp for schedule(static) nowait //first parallel pass
for (int i = 0; i<n; i++) {
sum += a[i];
s[i] = sum;
}
suma[ithread + 1] = sum;
#pragma omp barrier
#pragma omp single
{
double tmp = 0;
for (int i = 0; i<(nthreads + 1); i++) {
tmp += suma[i];
suma[i] = tmp;
}
}
__m128d offset = _mm_set1_pd(suma[ithread]);
#pragma omp for schedule(static) //second parallel pass with SSE as well
for (int i = 0; i<n/4; i++) {
__m128d tmp1 = _mm_load_pd(&s[4*i]);
tmp1 = _mm_add_pd(tmp1, offset);
__m128d tmp2 = _mm_load_pd(&s[4*i+2]);
tmp2 = _mm_add_pd(tmp2, offset);
_mm_store_pd(&s[4*i], tmp1);
_mm_store_pd(&s[4*i+2], tmp2);
}
}
delete[] suma;
}
Mi sembra che il compilatore come gcc/icc possa eseguire l'auto-vettorizzazione per la seconda parte, in modo da non dover utilizzare intrinseche SIMD. Otterrete miglioramenti delle prestazioni, confrontate con il semplice codice c con alcune opzioni del compilatore come '-msse2' – kangshiyin
Potrebbero. Lo spoglio su MSVC2013. Non auto-vettorializza il secondo passaggio. La mia esperienza con MSVC è che quando usi OpenMP devi fare da solo la vettorizzazione. Non penso che nessuno di loro srotoli il ciclo con il codice SSE per te, ma in questo caso non aiuta comunque. –
In risposta alla domanda sulle prestazioni, il codice generale che ho postato è oltre 3 volte più veloce del codice sequenziale in modalità di rilascio con AVX abilitato sul mio sistema di bridge ivy a 4 core. Il costo del tempo dovrebbe essere 'n/ncores * (1 + 1/SIMD_width)'. Quindi per 4 core e SIMD_width = 2 (doppio) che dovrebbe essere 3n/8. Si tratta di un aumento della velocità di 2,7 volte. L'hyper-threading aiuta un po ', quindi credo che lo stia spingendo oltre 3 (sto usando 8 thread, quando proverò 4 thread la performance diminuirà un po'). –