基數排序演算法的思想很簡單,但如何提高並行性,使得能用gpu進行高效的排序計算呢?本文介紹適合於gpu計算版本的排序演算法。
為了說明gpu版的排序演算法,我們先複習一下最初版本的排序演算法:
a picture is worth than a thousand words.因此我們上圖說話。
基數排序是從最低位看起,把這一位數字值相同的按照掃瞄順序放入同乙個桶裡面,值小的桶在前面。當所有數字都掃瞄完,再使用高一位,迴圈上述步驟,直至達到所有數字所有的最高位數,最後輸出的就是排序後的答案。
input:unsigned int inputvals[n]
output:unsigned int outputvals[n]
for(i=0; i
< numbins; i+= numbits)
1. 計算inputval中第i位值為0到numbins-1的各有多少個,其值分別放入binhistogram[numbins]中
2. 對binhistogram[numbins]進行scan計算,其值分別放入binscan[numbins]中,即
binscan[k]=binhistogram[0]+···+binhistogram[k-1]
3. 遍歷inputval陣列,計算第j個量時,計算其值所屬的桶bin的值
outputvals[binscan[bin]] = vals_src[j];
binscan[bin]++;
4. swap(inputvals,outputvals)
gpu版本的排序演算法與cpu在前兩步都是類似的。
for(i=0; i
< numbins; i+= numbits)
1. 同cpu
2. 同cpu
3. 決定每個數值的相對位置,比如:[001
1001]
-> [010
1232]
4. 根據第二步和第三步計算的結果,決定每個每個數字的絕對位置,通過map運算從相應的位置取值
終於到了我最想提醒大家的部分了。
1. 如何決定每個數值的相對位置?
決定每個數值的相對位置其實使用的是compact方法,以input為[0 0 1 1 0 0 1]
為例子,計算如下:
2. 但用以上的方法能算出真正的相對位置麼?
不能。
what?不能你還寫這麼久?
其實也不是完全不能,當排序的數目小於block中最大threads數目時(比如我設定的是2048),是可以正確排序的。為什麼呢?因為我們使用的scan方法中需要同步。
先複習hill/steele演算法,以加法為例,對[1, 2, 3, 4, 5, 6, 7, 8]進行scan計算
平行計算中最怕的就是沒能同步啦,那麼再上hill/steele演算法的**。
// scan
__global__ void scan_cal(unsigned
int *d_cdf, const
unsigned
int *d_bins, const size_t size)
// exclusive scan
d_cdf[myid]= d_bins[myid]-current_value;
__syncthreads();
}
發現問題了麼?在圖中每一行計算完後,需要等整個一行同步後才能進行下一次的計算。而__syncthreads();函式是block-level的同步,即在不同的block中的計算並不會同步後再進行,因而一些block沒有算完的時候,其他block已經取了之前的值接著下面的計算了。因此如果只有乙個block,即排序的數目小於block中最大threads數目時,還是可以正確排序的。
3. 那,怎麼辦呢?
這裡提供乙個可以排序220的方法。我們對之前的gpu演算法進行小的修改:
blocksize是乙個block裡面有多少個執行緒(blockdim.x×blockdim.y×blockdim.z),對於cuda計算能力1.x的,blocksize最大值為1024;1.x+的值為2048。
for(i=0; i
< numbins; i+= numbits)
1. 計算inputval的每blocksize個sub-array中,第i位值為0到numbins-1的各有多少個,其值分別放入binhistogram[numbins×griddim]中
2. 對binhistogram[numbins×griddim]進行scan計算,其值分別放入binscan[numbins×griddim]中,即
binscan[k]=binhistogram[0]+···+binhistogram[k-1]
3. block內決定每個數值的相對位置,比如:[001
1001]
-> [010
1232]
4. (不變)根據第二步和第三步計算的結果,決定每個每個數字的絕對位置,通過map運算從相應的位置取值
即分組compact。還是以[0 0 1 1 0 0 1]為例,此時我們設定blocksize=4,numbins=2(0和1),griddim=ceil(7/blocksize)=2:
binhistogram=[ 2 2 | 2 1]第二個2代表第二組中值為0的從位址2開始
第三個4代表第一組中值為1的從位址4開始
第四個6代表第二組中值為1的從位址6開始
計算值為0的相對位置
計算針對0的predicate: [true true false false | true true false]
其中true=1, false=0: [1 1 0 0 | 1 1 0]
exclusive scan: [0 1 2 2 | 0 1 2]
對於predicate為true的值,留下上一步的結果[0 1 nan nan | 0 1 nan]
計算值為1的相對位置
計算針對1的predicate:[false false true true | false false true]
其中true=1, false=0: [0 0 1 1 | 0 0 1]
exclusive scan: [0 0 0 1 | 0 0 0]
對於predicate為true的值,留下上一步的結果[0 1 0 1 | 0 1 1]
絕對位置=相對位置+binscan[bin的位置]
絕對位置=[ (0+0) (1+0) (0+4) (1+4) | (0+2) (1+2) (1+6) ]=[0 1 4 5 | 2 3 7]
__global__ void specific_address(unsigned
int *d_out_value, const
unsigned
int *const d_in_value,
const
unsigned
int *d_cdfs, const
unsigned
int *d_relative_pos,
const size_t numbins, const
unsigned
int pos, const size_t size)
之前提到的,上述演算法對最多220數字進行排序(對於計算能力1.x以上的顯示卡可以為222)。因為還是scan同步那裡進行了限制,因而還有修改的空間。因為我所做的工作計算量小於220因此就沒接著繼續研究了,有興趣的同學歡迎分享對更高資料集排序實現的idea,我們一起學習~ 排序 基數排序
基數排序 radix sort 是屬於 分配式排序 distribution sort 基數排序法又稱 桶子法 bucket sort 或bin sort,顧名思義,它是透過鍵值的部份資訊,將要排序的元素分配至某些 桶 中,藉以達到排序的作用。排序思想 首先按照資料的最低位 個位 將資料分配到0 9...
排序 基數排序
1 基數排序 桶排序 介紹 1 基數排序 radix sort 屬於 分配式排序 distribution sort 又稱 桶子法 bucket sort 或bin sort,顧名思義,它是通過鍵值的各個位的值,將要排序的元素分配至某些 桶 中,達到排序的作用 2 基數排序法是屬於穩定性的排序,基數...
桶排序 基數排序 計數基數排序 Java
前面已經講述了很多排序演算法,但是他們的排序演算法都是基於兩個值之間的比較,通過決策樹的方法可以證明深度為d的二叉樹則最多有 一些好的排序演算法是可以達到時間複雜度是線性的,桶排序就是其中一種。比如有n個數,但是這些數的最大數不超過m。這個時候就可以定義乙個含有m個元素的陣列 初始值為0 然後遍歷n...