2015-01-12 11 views
5

Ho estratto questa semplice funzione membro da un programma 2D più grande, tutto ciò che fa è un ciclo per accedere da tre array diversi e fare un'operazione matematica (convoluzione 1D). Ho testato con l'utilizzo di OpenMP per rendere questa particolare funzione più veloce:Perché questo ciclo non è più veloce con OpenMP?

void Image::convolve_lines() 
{ 
    const int *ptr0 = tmp_bufs[0]; 
    const int *ptr1 = tmp_bufs[1]; 
    const int *ptr2 = tmp_bufs[2]; 
    const int width = Width; 
#pragma omp parallel for 
    for (int x = 0; x < width; ++x) 
    { 
    const int sum = 0 
     + 1 * ptr0[x] 
     + 2 * ptr1[x] 
     + 1 * ptr2[x]; 
    output[x] = sum; 
    } 
} 

Se uso gcc 4.7 su Debian/amd64 ansimante il programma globale esegue molto più lento su una macchina 8 CPU. Se utilizzo gcc 4.9 su un debian/jessie amd64 (solo 4 CPU su questa macchina) il programma complessivo funziona con una differenza minima.

Utilizzo time confrontare: singola corsa nucleo:

$ ./test black.pgm out.pgm 94.28s user 6.20s system 84% cpu 1:58.56 total 

più run nucleo:

$ ./test black.pgm out.pgm 400.49s user 6.73s system 344% cpu 1:58.31 total 

Dove:

$ head -3 black.pgm 
P5 
65536 65536 
255 

Così Width è impostato 65536 durante l'esecuzione.

Se resto, sto usando cmake per la compilazione:

add_executable(test test.cxx) 
set_target_properties(test PROPERTIES COMPILE_FLAGS "-fopenmp" LINK_FLAGS "-fopenmp") 

E CMAKE_BUILD_TYPE è impostata su:

CMAKE_BUILD_TYPE:STRING=Release 

che implica -O3 -DNDEBUG

La mia domanda, perché è questo ciclo for non più veloce usando multi-core? Non c'è alcuna sovrapposizione nell'array, l'openmp dovrebbe dividere equamente la memoria. Non vedo da dove viene il collo di bottiglia?

Edit: Come è stato commentato, ho cambiato la mia file di input in:

$ head -3 black2.pgm 
P5 
33554432 128 
255 

Così Width è ora impostata durante l'esecuzione 33554432 (deve essere considerato da abbastanza). Ora la temporizzazione rivela:

singola corsa nucleo:

$ ./test ./black2.pgm out.pgm 100.55s user 5.77s system 83% cpu 2:06.86 total 

Multi run nucleo (per qualche motivo CPU% sia sempre inferiore al 100%, che indicherebbe filetti affatto):

$ ./test ./black2.pgm out.pgm 117.94s user 7.94s system 98% cpu 2:07.63 total 
+2

in generale, falso conflitto di condivisione/blocco. Inoltre, quanto è grande la larghezza? – sehe

+0

@se dispiace mi sono dimenticato di dirlo. – malat

+0

Come l'hai provato? Dubito che il singolo loop da 64k che hai dato richieda così tanto tempo. – ElderBug

risposta

2

Ho alcuni commenti generali:

1. Prima di ottimizzare il codice, assicurarsi che i dati siano allineati a 16 byte. Questo è estremamente importante per qualsiasi ottimizzazione si voglia applicare. E se i dati sono separati in 3 pezzi, è meglio aggiungere alcuni elementi fittizi per fare in modo che gli indirizzi iniziali dei 3 pezzi siano tutti allineati a 16 byte. In questo modo, la CPU può caricare facilmente i dati nelle linee della cache.

2. Assicurarsi che la funzione semplice sia vettorizzata prima di implementare openMP. La maggior parte dei casi, utilizzando i set di istruzioni AVX/SSE dovrebbe fornire un discreto miglioramento del thread singolo da 2 a 8X.Ed è molto semplice per il tuo caso: creare un registro mm256 costante e impostarlo con il valore 2 e caricare 8 numeri interi in tre registri mm256. Con il processore Haswell, è possibile eseguire un'aggiunta e una moltiplicazione insieme. Quindi, teoricamente, il ciclo dovrebbe accelerare di un fattore 12 se la pipeline AVX può essere riempita!

3. A volte parallelizzazione può ridurre le prestazioni: moderna CPU ha bisogno di diverse centinaia di migliaia di cicli di clock per riscaldarsi, entrando stati ad alte prestazioni e aumentare in proporzione la frequenza. Se l'attività non è abbastanza grande, è molto probabile che l'attività venga eseguita prima che la CPU si scaldi e non si possa aumentare la velocità andando in parallelo. E non dimenticare che openMP ha anche un overhead: creazione di thread, sincronizzazione e cancellazione. Un altro caso è la scarsa gestione della memoria. Gli accessi ai dati sono così sparpagliati, tutti i core della CPU sono inattivi e in attesa di dati dalla RAM.

Il mio suggerimento:

Si potrebbe provare a Intel MKL, non reinventare la ruota. La libreria è ottimizzata fino all'estremo e non viene sprecato alcun ciclo di clock. Uno può collegarsi con la libreria seriale o la versione parallela, un aumento di velocità è garantito se è possibile andando in parallelo.

Problemi correlati