我有一个代码来计算使用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的幂)。 所以仍有三个(可能是相关的)问题 -
我怎样才能让这个内核更快?在我必须循环每个tid的情况下使用共享内存是一个好主意吗?
为什么只为某些数据尺寸生成正确的结果?
我如何修改减少量?
__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);
}
答案 0 :(得分:3)
虽然我自称对素数的筛选一无所知,但GPU版本中存在许多正确性问题,无论您实施的算法是否正确,都会阻止其正常工作:
__syncthreads()
来电必须是无条件的。编写代码是不正确的,其中分支差异可能会使同一warp中的某些线程无法执行__syncthreads()
调用。基础PTX为bar.sync
,PTX指南说明:
障碍是在每个warp的基础上执行的,就像a中的所有线程一样 扭曲是活跃的。因此,如果warp中的任何线程执行一个bar 指令,就好像warp中的所有线程都执行了 酒吧指导。经线中的所有线程都会停止,直到屏障 完成,屏障的到达计数增加 warp大小(不是warp中活动线程的数量)。在 有条件执行的代码,只有在使用条形指令时才应使用 众所周知,所有线程都以相同的方式评估条件( 扭曲并不分歧)。由于障碍是在每个经线上执行的 基础,可选的线程数必须是warp大小的倍数。
s_flags
设置为1。当然这不是代码的意图吗?volatile
,以防止编译器优化可能导致共享内存减少。如果您修复了这些问题,代码可能会起作用。性能是一个完全不同的问题。当然在较旧的硬件上,整数模运算非常非常慢,不推荐使用。我记得读过一些材料,表明Sieve of Atkin是在GPU上快速生成素数的有用方法。