2013-02-28 8 views
6

un follow-up a Q: EarlyExit e DroppedThreadssyncthreads condizionali e stallo (o non)

Secondo i link di cui sopra, il codice qui sotto dovrebbe deadlock.
Si prega di spiegare perché questo NON si blocca. (Cuda 5 su un Fermi)

__device__ int add[144]; 
__device__ int result; 

add<<<1,96>>>(); // the calling 

__global__ void add() { 
for(idx=72>>1; idx>0; idx>>=1) { 
    if(thrdIdx < idx) 
    add[thrdIdx]+= add[thrdIdx+idx]; 
    else 
    return; 
    __syncthreads(); 
} 

if(thrdIdx == 0) 
    result= add[0]; 
} 
+1

qual è la configurazione di avvio? (ad es. blocco e dimensioni della griglia) – alrikai

+4

Il codice che hai fornito non si avvicina alla compilazione, tanto meno al deadlock. A proposito, il deadlock dovuto all'uso improprio dei syncthreads è una * possibilità * non una * garanzia *. Il modo corretto di affrontare l'uso improprio dei syncthreads è di concludere che il comportamento è [non definito] (http://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#synchronization-functions). –

risposta

9

Questo è tecnicamente un programma mal definito.

La maggior parte, ma non tutte (ad esempio, G80 non lo è), le GPU NVIDIA supportano l'uscita anticipata in questo modo perché l'hardware mantiene un conteggio thread attivo per ogni blocco e questo conteggio viene utilizzato per la sincronizzazione della barriera anziché il thread iniziale conta per il blocco.

Pertanto, quando viene raggiunto il numero __syncthreads() nel codice, l'hardware non attende i thread che sono già stati restituiti e il programma viene eseguito senza deadlock.

Un uso più comune di questo stile è:

__global__ void foo(int n, ...) { 
    int idx = threadIdx.x + blockIdx.x * blockDim.x; 
    if (idx >= n) return; 
    ... // do some computation with remaining threads 
} 

Nota importante: conta barriera vengono aggiornate per-ordito (vedi here), non per-thread. Quindi potresti avere il caso in cui, per esempio, solo pochi (o zero) thread ritornano presto. Ciò significa che il conteggio delle barriere non viene decrementato. Tuttavia, finché almeno un filo di ciascun ordito raggiunge la barriera, non si bloccherà.

Quindi, in generale, è necessario utilizzare le barriere con attenzione. Ma in particolare, (semplici) i primi schemi di uscita come questo funzionano.

Modifica: per il caso specifico.

Iterazione Idx == 36: 2 orditi attivi in ​​modo che il conteggio delle uscite di barriera sia 64. Tutti i fili da ordito 0 raggiungono la barriera, conteggio incrementale da 0 a 32. 4 fili dalla barriera di curvatura 1, conteggio incrementale da 32 a 64, e gli orditi 0 e 1 vengono rilasciati dalla barriera. Leggi il link qui sopra per capire perché questo accade.

Iteration Idx == 18: 1 warp attivo in modo che il conteggio delle uscite di barriera sia 32. 18 thread da warp 0 raggiungere barriera, conteggio incrementale da 0 a 32. La barriera è soddisfatta e l'ordito 0 viene rilasciato.

Ecc ...

+1

Non lasciarti coinvolgere da ciò che fa la funzione, ma guarda come lo sta facendo. Altri Q su questo argomento sembrano implicare "Oh non puoi farlo". La mia esperienza dice che altrimenti. Sembra funzionare in modo affidabile, non indefinito. Sto cercando di capire perché, quindi posso farne un uso migliore. I # utilizzati sopra iniziano w/2 WARP e rapidamente cadono su uno. Thrds sono caduti lungo la strada. I thrds nel secondo warp, tutti vedono i primi ritorni. Quindi non vedono la barriera mentre il 1 ° WARP è ancora in esecuzione. Questo non sembra essere d'accordo con il tuo commento "non si bloccherà". Puoi elaborare? – Doug

+0

Vedere la mia modifica. Speriamo sia chiaro ora. – harrism

+0

Sì, il conteggio dei thread attivi (quelli che non ritornano in anticipo) e il conteggio delle barriere sono di 2 # diversi. Rispondete da un diverso punto di vista: finché c'è almeno un filo attivo in ogni ordito attivo, non si bloccherà. – Doug