用于计算素数的CUDA的并行减少

时间:2011-09-26 07:56:17

标签: cuda primes reduction

我有一个代码来计算使用OpenMP并行化的素数:

    #pragma omp parallel for private(i,j) reduction(+:pcount) schedule(dynamic)
    for (i = sqrt_limit+1; i < limit; i++)
    {
            check = 1;
            for (j = 2; j <= sqrt_limit; j++)
            {

                    if ( !(j&1) && (i&(j-1)) == 0 )
                    {
                            check = 0;
                            break;
                    }

                    if ( j&1 && i%j == 0 )
                    {
                            check = 0;
                            break;
                    }

            }
            if (check)
                pcount++;

    }

我正在尝试将其移植到GPU,我希望像上面的OpenMP示例那样减少计数。以下是我的代码,除了提供不正确的结果之外,它也更慢:

__global__ void sieve ( int *flags, int *o_flags, long int sqrootN, long int N) 
{
    long int gid = blockIdx.x*blockDim.x+threadIdx.x, tid = threadIdx.x, j;
    __shared__ int s_flags[NTHREADS];

    if (gid > sqrootN && gid < N)
            s_flags[tid] = flags[gid];
    else
            return;
    __syncthreads();

    s_flags[tid] = 1;

    for (j = 2; j <= sqrootN; j++)
    {
            if ( gid%j == 0 )
            {
                    s_flags[tid] = 0;
                    break;
            }
    }
    //reduce        
    for(unsigned int s=1; s < blockDim.x; s*=2)
    {
            if( tid % (2*s) == 0 )
            {
                    s_flags[tid] += s_flags[tid + s];
            }
            __syncthreads();
    }
    //write results of this block to the global memory
    if (tid == 0)
            o_flags[blockIdx.x] = s_flags[0];

}

首先,我如何快速制作这个内核,我认为瓶颈是for循环,我不知道如何替换它。接下来,我的计数不正确。我确实更改了'%'运算符并发现了一些好处。

flags数组中,我标记了从2到sqroot(N)的素数,在这个内核中我正在计算从sqroot(N)到N的素数,但我需要检查每个数字是否在{sqroot(N),N}可以被{2,sqroot(N)}中的素数整除。 o_flags数组存储每个块的部分和。


编辑:根据建议,我修改了我的代码(我现在更了解syncthreads的评论);我意识到我不需要flags数组,只有全局索引在我的情况下工作。在这一点上我关心的是代码的缓慢(超过正确性)可归因于for循环。此外,在某个数据大小(100000)之后,内核对后续数据大小产生了错误的结果。即使数据大小小于100000,GPU缩减结果也是不正确的(NVidia论坛的一位成员指出,这可能是因为我的数据大小不是2的幂)。 所以仍有三个(可能是相关的)问题 -

  1. 我怎样才能让这个内核更快?在我必须循环每个tid的情况下使用共享内存是一个好主意吗?

  2. 为什么只为某些数据尺寸生成正确的结果?

  3. 我如何修改减少量?

    __global__ void sieve ( int *o_flags, long int sqrootN, long int N )
    {
    unsigned int gid = blockIdx.x*blockDim.x+threadIdx.x, tid = threadIdx.x;
    volatile __shared__ int s_flags[NTHREADS];
    
    s_flags[tid] = 1;
    for (unsigned int j=2; j<=sqrootN; j++)
    {
           if ( gid % j == 0 )
                s_flags[tid] = 0;
    }
    
    __syncthreads();
    //reduce
    reduce(s_flags, tid, o_flags);
    }
    

1 个答案:

答案 0 :(得分:3)

虽然我自称对素数的筛选一无所知,但GPU版本中存在许多正确性问题,无论您实施的算法是否正确,都会阻止其正常工作:

  1. __syncthreads()来电必须是无条件的。编写代码是不正确的,其中分支差异可能会使同一warp中的某些线程无法执行__syncthreads()调用。基础PTX为bar.sync,PTX指南说明:
      

    障碍是在每个warp的基础上执行的,就像a中的所有线程一样   扭曲是活跃的。因此,如果warp中的任何线程执行一个bar   指令,就好像warp中的所有线程都执行了   酒吧指导。经线中的所有线程都会停止,直到屏障   完成,屏障的到达计数增加   warp大小(不是warp中活动线程的数量)。在   有条件执行的代码,只有在使用条形指令时才应使用   众所周知,所有线程都以相同的方式评估条件(   扭曲并不分歧)。由于障碍是在每个经线上执行的   基础,可选的线程数必须是warp大小的倍数。

  2.   
  3. 在有条件地从全局内存中加载某些值之后,您的代码无条件地将s_flags设置为1。当然这不是代码的意图吗?
  4.   
  5. 代码缺少筛选代码和减少之间的同步障碍,这可能导致共享内存竞争和减少的错误结果。
  6.   
  7. 如果您计划在Fermi类卡上运行此代码,则应将共享内存数组声明为volatile,以防止编译器优化可能导致共享内存减少。
  8.   

    如果您修复了这些问题,代码可能会起作用。性能是一个完全不同的问题。当然在较旧的硬件上,整数模运算非常非常慢,不推荐使用。我记得读过一些材料,表明Sieve of Atkin是在GPU上快速生成素数的有用方法。