CUDA,使用减少找到Max,错误

时间:2011-06-28 17:11:39

标签: cuda max reduction

这是我的代码尝试进行缩减以在块中找到最多50个值的数组。我已将数组填充到64.

对于线程1-31,我有正确的maxVal打印输出但是对于线程32-49,它是一个完全随机的数字。我不知道我做错了什么。

顺便说一句。我以为我不需要在展开时_sync每行,但显然我必须这样做。有什么建议吗?

提前感谢您的帮助。

//block size = 50


__syncthreads();

if (tid<32){

    cptmp[tid]=(cptmp[tid]< cptmp[tid+32]) ? cptmp[tid+32] : cptmp[tid];__syncthreads();    
    cptmp[tid]=(cptmp[tid]< cptmp[tid+16]) ? cptmp[tid+16] : cptmp[tid];__syncthreads();
    cptmp[tid]=(cptmp[tid]< cptmp[tid+8]) ? cptmp[tid+8] : cptmp[tid];  __syncthreads();    
    cptmp[tid]=(cptmp[tid]< cptmp[tid+4]) ? cptmp[tid+4] : cptmp[tid];  __syncthreads();
    cptmp[tid]=(cptmp[tid]< cptmp[tid+2]) ? cptmp[tid+2] : cptmp[tid];  __syncthreads();    
    cptmp[tid]=(cptmp[tid]< cptmp[tid+1]) ? cptmp[tid+1] : cptmp[tid];  __syncthreads();

}

__syncthreads();

//if (tid==0) {
    maxVal=cptmp[0];
    if(bix==0 && biy==0) cuPrintf(" max:%f x:%d y:%d\n", maxVal, blockIdx.x, blockIdx.y);
//}

3 个答案:

答案 0 :(得分:3)

这是一个更有效(至少在Fermi GPU上)和使用volatile的正确代码。将T替换为您的类型(或使用模板):

if (tid<32) {
    volatile T *c = cptmp;
    T t = c[tid];
    c[tid] = t = (t < c[tid+32]) ? c[tid+32] : t;
    c[tid] = t = (t < c[tid+16]) ? c[tid+16] : t;
    c[tid] = t = (t < c[tid+ 8]) ? c[tid+ 8] : t;
    c[tid] = t = (t < c[tid+ 4]) ? c[tid+ 4] : t;
    c[tid] = t = (t < c[tid+ 2]) ? c[tid+ 2] : t;
    c[tid] = t = (t < c[tid+ 1]) ? c[tid+ 1] : t;
}

为什么效率更高?好吧,为了在没有__syncthreads()的情况下的正确性,我们必须使用指向共享内存的易失性指针。但这迫使编译器“尊重”对共享内存的所有读取和写入 - 它无法优化并保留寄存器中的任何内容。因此,通过明确地始终将c[tid]保留在临时t中,我们为每行代码保存一个共享内存负载。由于Fermi是一种加载/存储架构,只能使用寄存器作为指令操作数,这意味着我们每行保存一条指令,或总共6条指令(总体上约为25%)。

在旧的T10 / GT200体系结构及更早版本中,您的代码(具有volatile和no __syncthreads())同样有效,因为该体系结构可以直接从共享内存中为每条指令提供一个操作数。

如果您希望if优先于?:

,则此代码应相同
if (tid<32) {
    volatile T *c = cptmp;
    T t = c[tid];
    if (t < c[tid+32]) c[tid] = t = c[tid+32];
    if (t < c[tid+16]) c[tid] = t = c[tid+16];
    if (t < c[tid+ 8]) c[tid] = t = c[tid+ 8];
    if (t < c[tid+ 4]) c[tid] = t = c[tid+ 4];
    if (t < c[tid+ 2]) c[tid] = t = c[tid+ 2];
    if (t < c[tid+ 1]) c[tid] = t = c[tid+ 1];
}

答案 1 :(得分:2)

不要在不同的代码中使用__syncthreads()! 来自给定块的所有线程或没有线程都应该到达同一位置的每个__syncthreads()

来自单个warp(32个线程)的所有线程都是隐式同步的,因此您不需要__syncthreads()将它们全部放在一起。但是,如果您担心同一个warp的另一个线程可能无法看到一个线程的共享内存写入,请使用__threadfence_block()

详细阐述__threadfence_block()的重要性。请考虑以下两行:

cptmp[tid]=(cptmp[tid]< cptmp[tid+2]) ? cptmp[tid+2] : cptmp[tid];
cptmp[tid]=(cptmp[tid]< cptmp[tid+1]) ? cptmp[tid+1] : cptmp[tid];

它可以编译成这样的东西:

int tmp; //assuming that cptmp is an array of int-s
tmp=cptmp[tid];
tmp=(tmp<cptmp[tid+2])?cptmp[tid+2]:tmp;
tmp=(tmp<cptmp[tid+1])?cptmp[tid+1]:tmp;
cptmp[tid]=tmp;

虽然对于单线程代码来说是正确的,但对于CUDA来说显然是失败的。

要防止这样的优化,请将cptmp数组声明为volatile,或在行之间添加此__threadfence_block()。该函数确保同一块的所有线程在函数存在之前看到当前线程的共享内存写入。

存在类似的__threadfence()函数以确保全局内存可见性。

答案 2 :(得分:1)

对于今后偶然发现这个主题的人,正如我所做的那样,除了哈里斯的回答之外,这里还有一个建议 - 从性能的角度来看,考虑随机操作可能是值得的,所以更新后的代码最大化使用单次扭曲的64个元素将如下所示:

auto localMax = max(c[tid], c[tid + 32]);    
for (auto i = 16; i >= 1; i /= 2)
{
    localMax = max(localMax, __shfl_xor(localMax, i));
}
c[tid] = localMax;

只需要两次读取和一次全局内存写入,因此非常简洁。