Ich versuche, meine Histogramm-Berechnungen in CUDA zu optimieren. Es gibt mir eine ausgezeichnete Beschleunigung gegenüber der entsprechenden OpenMP-CPU-Berechnung. Ich vermute jedoch (nach Intuition), dass die meisten Pixel in ein paar Eimer fallen. Nehmen wir an, dass wir 256 Pixel haben, die in zwei Buckets fallen.Beschleunigen CUDA Atomic Berechnung für viele Bins/einige Bins
Der einfachste Weg, es zu tun ist, es zu tun
- laden die Variablen in den gemeinsamen Speicher
- Sie vektorisiert Lasten für unsigned char usw. zu sein scheint, wenn nötig.
- ein Atom Fügen Sie im gemeinsam genutzten Speicher
- auf globale einen verschmolzenen schreiben Sie.
Etwas wie folgt:
__global__ void shmem_atomics_reducer(int *data, int *count){
uint tid = blockIdx.x*blockDim.x + threadIdx.x;
__shared__ int block_reduced[NUM_THREADS_PER_BLOCK];
block_reduced[threadIdx.x] = 0;
__syncthreads();
atomicAdd(&block_reduced[data[tid]],1);
__syncthreads();
for(int i=threadIdx.x; i<NUM_BINS; i+=NUM_BINS)
atomicAdd(&count[i],block_reduced[i]);
}
Die Leistung dieses kernel fällt (natürlich), wenn wir eine Verringerung der Anzahl der Bins, von etwa 45 GB/s bei 32 Behältern auf etwa 10 GB/s bei 1 Tonne. Konflikte und geteilte Speicherbankkonflikte werden als Gründe angegeben. Ich weiß nicht, ob es irgendeinen Weg gibt, um einen von diesen für diese Berechnung in bedeutender Weise zu entfernen.
Ich habe auch mit einer anderen (schönen) Idee aus dem Parallelforall-Blog experimentiert, die Warp-Level-Reduzierungen mit __ballot zum Erfassen von Warp-Ergebnissen und dann mit __popc() zur Reduzierung des Warp-Levels verwendet.
__global__ void ballot_popc_reducer(int *data, int *count){
uint tid = blockIdx.x*blockDim.x + threadIdx.x;
uint warp_id = threadIdx.x >> 5;
//need lane_ids since we are going warp level
uint lane_id = threadIdx.x%32;
//for ballot
uint warp_set_bits=0;
//to store warp level sum
__shared__ uint warp_reduced_count[NUM_WARPS_PER_BLOCK];
//shared data
__shared__ uint s_data[NUM_THREADS_PER_BLOCK];
//load shared data - could store to registers
s_data[threadIdx.x] = data[tid];
__syncthreads();
//suspicious loop - I think we need more parallelism
for(int i=0; i<NUM_BINS; i++){
warp_set_bits = __ballot(s_data[threadIdx.x]==i);
if(lane_id==0){
warp_reduced_count[warp_id] = __popc(warp_set_bits);
}
__syncthreads();
//do warp level reduce
//could use shfl, but it does not change the overall picture
if(warp_id==0){
int t = threadIdx.x;
for(int j = NUM_WARPS_PER_BLOCK/2; j>0; j>>=1){
if(t<j) warp_reduced_count[t] += warp_reduced_count[t+j];
__syncthreads();
}
}
__syncthreads();
if(threadIdx.x==0){
atomicAdd(&count[i],warp_reduced_count[0]);
}
}
}
Dies gibt anständige Zahlen (gut, dass strittig ist - Spitzengerät mem bw 133 GB/s, die Dinge scheinen auf Startkonfiguration abhängig) für die einzelnen Behälter Fall (35-40 GB/s für 1 bin, verglichen mit 10-15 GB/s mit Atomics), aber die Leistung sinkt drastisch, wenn wir die Anzahl der Bins erhöhen. Wenn wir mit 32 Bins arbeiten, sinkt die Leistung auf etwa 5 GB/s. Der Grund könnte vielleicht darin liegen, dass der einzelne Thread alle Bins durchläuft und nach der Parallelisierung der NUM_BINS-Schleife fragt.
Ich habe mehrere Möglichkeiten der Parallelisierung der NUM_BINS-Schleife versucht, von denen keine ordnungsgemäß zu funktionieren scheint. Zum Beispiel könnte man (sehr unelegant) den Kernel manipulieren, um einige Blöcke für jedes Bin zu erzeugen. Dies scheint sich auf die gleiche Weise zu verhalten, möglicherweise, weil wir wieder unter dem Konflikt mit mehreren Blöcken leiden würden, die versuchen, aus dem globalen Speicher zu lesen. Plus, die Programmierung ist klobig. Gleichermaßen ergibt die Parallelisierung in der y-Richtung für Behälter ähnlich wenig inspirierende Ergebnisse.
Die andere Idee, die ich nur für Kicks versuchte, war dynamische Parallelität, Starten eines Kernels für jeden Behälter. Dies war katastrophal langsam, möglicherweise aufgrund der Tatsache, dass keine echte Rechenarbeit für die Kind-Kernel und den Start-Overhead geleistet wurde.
Der vielversprechendste Ansatz scheint zu sein - von Nicholas Wilts article
zur Verwendung der sogenannten privatisierten Histogramme enthalten Bins für jeden Thread im gemeinsam genutzten Speicher, die angeblich sehr schwer auf shmem Nutzung wäre (und wir nur haben 48 kB pro SM auf Maxwell).
Vielleicht könnte jemand Einblick in das Problem geben? Ich finde, dass man stattdessen den Algorithmus ändern sollte, um keine Histogramme zu verwenden, um etwas weniger Frequentist zu verwenden. Ansonsten nehme ich einfach die Atomic-Version.
Bearbeiten: Der Kontext für mein Problem besteht in der Berechnung von Wahrscheinlichkeitsdichtefunktionen, die für die Musterklassifizierung verwendet werden sollen. Wir können ungefähre Histogramme (genauer gesagt, PDFs) berechnen, indem wir nichtparametrische Methoden wie Parzen Windows oder Kernel Density Estimation verwenden. Dies überwindet jedoch nicht das Problem der Dimensionalität, da wir alle Datenpunkte für jeden Behälter summieren müssen, was teuer wird, wenn die Anzahl von Behältern groß wird. Siehe hier: Parzen
Wie Ihre Frage steht, ist es wahrscheinlich wegen geschlossen werden sollte „Not klar, was Sie fragen "- Sie sind an einigen Stellen ein wenig vage, vor allem über die genauen Einschränkungen Ihres Problems, was Sie erwarten, in Ihrem" sagen wir mal "Beispiel und so weiter passieren. Außerdem fragen Sie im Grunde eher nach einer Meinung als nach einer konkreten Antwort auf eine Frage, was ein weiterer Grund zum Schließen ist. Allerdings arbeite ich persönlich an fast der gleichen Sache, also bin ich voreingenommen. Jedenfalls würde ich gerne meine Meinung abgeben - abseits der Baustelle. – einpoklum
Ich habe ein [Zimmer] (http://chat.stackoverflow.com/rooms/125842) erstellt, wenn du weiter reden möchtest. – einpoklum
Ich war eigentlich auf der Suche nach etwas in der Art eines Leitfadens zur Histogramm- und Atomberechnung, wenn die Eingänge stark entartet sind. Gerne zu diskutieren. – kakrafoon