Radix sort 基數排序

有關《GPU並行程序設計》(英文《CUDA Programming A Developer’s Guide to Parallel Computing with GPUs》) 第六章 中基數排序,其中並行排序的多線程排序,由於沒有具體較爲詳細的介紹,對於初次接觸多線程的人略微困難。本文較爲詳細的介紹此多線程基數排序代碼。

一:在解釋多線程的代碼之前,先解釋下單線程的串行代碼,如下:

__host__ void cpu_sort(u32 * const data,const u32 num_elements)
{
        static u32 cpu_tmp_0[NUM_ELEM];
        static u32 cpu_tmp_1[NUM_ELEM];

        for (u32 bit=0;bit<32;bit++)
        {
                u32 base_cnt_0 = 0;
                u32 base_cnt_1 = 0;

                for (u32 i=0; i<num_elements; i++)
                {
                        const u32 d = data[i];
                        const u32 bit_mask = (1 << bit);
                        if ( (d & bit_mask) > 0 )
                        {
                                cpu_tmp_1[base_cnt_1] = d;
                                base_cnt_1++;
                        }
                        else
                        {
                                cpu_tmp_0[base_cnt_0] = d;
                                base_cnt_0++;
                        }
                }
                // Copy data back to source - first the zero list
                 for (u32 i=0; i<base_cnt_0; i++)
                 {
                         data[i] = cpu_tmp_0[i];
                 }
                 // Copy data back to source - then the one list
                 for (u32 i=0; i<base_cnt_1; i++)
                 {
                         data[base_cnt_0+i] = cpu_tmp_1[i];
                 }
        }
}

串行代碼執行過程,假設有 4 個整型數據元素的數組 data ,並創建兩臨時空間cpu_tmp_0,cpu_tmp_1 如下圖:



只知考慮外循環bit=0時的循環情況,內層循環變量data中所有元素,執行過程如下圖:
內存循環首先訪問第一個元素4,判斷4的末尾bit是0,所有將元素4存儲到cpu_tmp_0的第一個位置;然後訪問第二個元素3,判斷3的末尾bit是1,所有將元素3存儲到cpu_tmp_1的第一個位置;以此類推:以此方位第三、四個元素,分別存儲到cpu_tmp_0、cpu_tmp_1的第二個位置。最後先將cpu_tmp_0的元素拷貝到data中對應位置中,在拷貝cpu_tmp_1中的元素。
當bit爲其它值是,內層循環重複之前的計算過程。這個計算完成之後形成一個有序序列!

二:如果理解的單線程的計算過程,那麼多線程與單線程的區別在於:
1、每個線程訪問的元素不再連續的,而是步長爲n,n爲線程總數。
2、計算結束之後,形成了n個有序的序列。
先看下多線程代碼,如下:
__device__ void radix_sort(u32 * const sort_tmp,const u32 num_lists,
			        const u32 num_elements,const u32 tid,
				u32 * const sort_tmp_0,u32 * const sort_tmp_1)
{
        // Sort into num_list, lists
        // Apply radix sort on 32 bits of data
        for (u32 bit=0;bit<32;bit++)
        {
                u32 base_cnt_0 = 0;
                u32 base_cnt_1 = 0;
                const u32 bit_mask = (1 << bit);
                for (u32 i=0; i<num_elements; i+=<strong>num_lists</strong>)
                {
                        const u32 elem = sort_tmp[i+tid];
                        if ( (elem & bit_mask) > 0 )
                        {
                                sort_tmp_1[base_cnt_1+tid] = elem;
                                base_cnt_1+=num_lists;
                        }
                        else
                        {
                                sort_tmp_0[base_cnt_0+tid] = elem;
                                base_cnt_0+=num_lists;
                        }
                }
        }
        // Copy data back to source - first the zero list
        for (u32 i=0; i<base_cnt_0; i+=num_lists)
        {
                sort_tmp[i+tid] = sort_tmp_0[i+tid];
        }
        // Copy data back to source - then the one list
        for (u32 i=0; i<base_cnt_1; i+=num_lists)
        {
                sort_tmp[base_cnt_0+i+tid] = sort_tmp_1[i+tid];
        }
       __syncthreads();
}


在多線程中變量 base_cnt_0,base_cnt_1爲每個線程私有變量,每個線程的步長爲num_lists,num_lists在這裏等於block中的線程數

在這裏也只知考慮外循環bit=0時的循環情況,假設內層循環變量sort_tmp中共有如上所示16個元素,假設只有1個block,block中有4個線程,那麼總共就是4個線程。那麼num_lists的值就是4,即:每個線程的步長是4.
如上圖所示,4個線程每個線程處理一個元素,每次循環就會處理4個元素。共有16個元素,也就是循環4次。0號線程只處理索引爲0、4、8、12的元素(紅色塊),同理,1號線程只處理索引是1、5、9、13(黃色快);2號線程只處理索引爲2、6、10、14的元素(藍色快);3號線程只處理索引爲3、7、11、15的元素(綠色塊)。
只看0號線程,紅色塊部分,處理過程,如下圖:

0號線程只處理紅色塊的數據元素,且只能將sort_tmp中元素存放到,sort_tmp_0和sort_tmp_1中對應的紅色位置內,最後在將sort_tmp_0和sort_tmp_1中的元素拷貝回sort_tmp。
同理,對於1、2、3號線程結果如下,


這是bit=0的結果,bit爲其它值,處理過程也是一樣的。最終將會得到4個有序的序列。即:紅、黃、藍、綠色塊分別組成的序列。
至此基數排序結束。當然要想得到最後的一個完成的有序序列,則需後面章節講到的-合併。
(完)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章