GPU排序与CPU排序

时间:2015-05-08 12:08:48

标签: algorithm sorting cuda gpgpu mergesort

我做了一个非常天真的mergesort算法实现,我转向使用CUDA进行非常小的实现更改,算法代码如下:

//Merge for mergesort
__device__ void merge(int* aux,int* data,int l,int m,int r)
{
    int i,j,k;
    for(i=m+1;i>l;i--){
        aux[i-1]=data[i-1];
    }
    //Copy in reverse order the second subarray
    for(j=m;j<r;j++){
        aux[r+m-j]=data[j+1];
    }
    //Merge
    for(k=l;k<=r;k++){
        if(aux[j]<aux[i] || i==(m+1))
            data[k]=aux[j--];
        else
            data[k]=aux[i++];
    }
}

//What this code do is performing a local merge
//of the array
__global__
void basic_merge(int* aux, int* data,int n)
{
    int i = blockIdx.x*blockDim.x + threadIdx.x;
    int tn = n / (blockDim.x*gridDim.x);
    int l = i * tn;
    int r = l + tn;
    //printf("Thread %d:  %d,%d: \n",i,l,r);
    for(int i{1};i<=(tn/2)+1;i*=2)
        for(int j{l+i};j<(r+1);j+=2*i)
        {
            merge(aux,data,j-i,j-1,j+i-1);
        }
    __syncthreads();
    if(i==0){
        //Complete the merge
        do{
            for(int i{tn};i<(n+1);i+=2*tn)
                merge(aux,data,i-tn,i-1,i+tn-1);
            tn*=2;
        }while(tn<(n/2)+1);
    }
}

问题在于,无论我在GTX 760上启动多少线程,排序性能总是比在8个线程上运行的CPU上的相同代码差得多(我的CPU最多支持8个并发线程的硬件支持) )。

例如,在CPU上排序1.5亿个元素需要几百毫秒,在GPU上最多10分钟(即使每个块有1024个线程)!很明显,我在这里错过了一些重要的观点,请你给我一些评论吗?我强烈怀疑问题是在第一个线程执行的最终合并操作中,此时我们有一定数量的子数组(确切的数量取决于线程数),这些数据已经排序并需要我合并,这是仅由一个线程(一个小的GPU线程)完成。

我认为我应该在这里使用减少类型,因此每个线程并行执行更多的合并,并且&#34;完成合并&#34;步骤只是合并最后两个排序的子数组..

我对CUDA很新。

编辑(ADDENDUM):

感谢您的链接,我必须承认,在充分利用该材料之前,我还需要一些时间来学习更好的CUDA。无论如何,我能够重写排序功能,以便尽可能多地利用多个线程,我的第一个实现在合并过程的最后阶段有一个瓶颈,它只由一个多处理器执行。

现在在第一次合并之后,我每次使用最多(1/2)*(n / b)个线程,其中n是要排序的数据量,b是按每个排序的数据块的大小线程。

性能方面的改进令人惊讶,仅使用1024个线程就需要大约10秒才能对30万个元素进行排序..不幸的是,这仍然是一个糟糕的结果!问题在于线程同步,但首先,让我们看看代码:

__global__
void basic_merge(int* aux, int* data,int n)
{
    int k = blockIdx.x*blockDim.x + threadIdx.x;
    int b = log2( ceil( (double)n / (blockDim.x*gridDim.x)) ) + 1;
    b = pow( (float)2, b);
    int l=k*b;
    int r=min(l+b-1,n-1);
    __syncthreads();
    for(int m{1};m<=(r-l);m=2*m)
    {
        for(int i{l};i<=r;i+=2*m)
        {
            merge(aux,data,i,min(r,i+m-1),min(r,i+2*m-1));
        }
    }
    __syncthreads();
    do{
        if(k<=(n/b)*.5)
        {
            l=2*k*b;
            r=min(l+2*b-1,n-1);
            merge(aux,data,l,min(r,l+b-1),r);
        }else break;
        __syncthreads();
        b*=2;
    }while((r+1)<n);
}

功能&#39;合并&#39;和以前一样。现在问题是我只使用了1024个线程而不是65000个以及更多我可以在我的CUDA设备上运行,问题是__syncthreads在网格级别不能用作同步原语,而只是在块级别!

所以我可以同步最多1024个线程,即每个块支持的线程数量。没有适当的同步,每个线程都会弄乱另一个线程的数据,并且合并过程不起作用。

为了提高性能,我需要在网格中的所有线程之间进行某种同步,似乎没有用于此目的的API,并且我读到了涉及从主机代码启动多个内核的解决方案,使用主机作为所有线程的障碍。

我对如何在mergesort函数中实现此tehcnique有一定的计划,我将在不久的将来为您提供代码。你有自己的建议吗?

由于

1 个答案:

答案 0 :(得分:1)

看起来所有工作都在__global __ memory中完成。每次写入都需要很长时间,每次读取都需要很长时间才能使函数变慢。我认为首先将数据复制到__shared __ memory然后在那里完成工作然后在完成排序(对于该块)时将结果复制回全局内存会有所帮助。

全局内存大约需要400个时钟周期(如果数据恰好位于L2缓存中,则大约为100个)。另一方面,共享存储器只需要1-3个时钟周期进行写入和读取。

以上内容对性能有很大帮助。您可以尝试的其他一些超级小事...... (1)删除第一个__syncthreads();它并没有真正做任何事情,因为此时warps之间没有数据过去。 (2)移动&#34; int b = log2(ceil((double)n /(blockDim.x * gridDim.x)))+ 1; b = pow((float)2,b);&#34;在内核之外,只是传入b而不是。只有在真正需要计算一次时才会反复计算。

我尝试按照您的算法进行操作,但无法进行。变量名很难遵循......或者......你的代码在我的头上,我无法遵循。 =)希望以上有所帮助。