为什么额外的__syncthreads()调用会导致意外行为?

时间:2015-02-18 17:36:56

标签: cuda

我在CUDA样本中使用了改编自threadFenceReduction的缩减代码,这也在this presentation (PDF)中进行了描述。

在做一些调试时,我发现只是插入一个额外的__syncthreads()调用,减少不再给出正确的总和:

typedef int64_t SumType;

template <int blockSize>
static __device__ void
reduceBlock(
    SumType mySum,
    const unsigned int tid
    )
{
    // Each thread puts its local sum into shared memory 
    extern __shared__ SumType sdata[];
    sdata[tid] = mySum;
    __syncthreads();

    // Sum values at an offset of 128 and 64
    if( blockSize >= 256 ) { if (tid < 128) { sdata[tid] = mySum = mySum + (sdata[tid + 128]); } __syncthreads(); }
    if( blockSize >= 128 ) { if (tid <  64) { sdata[tid] = mySum = mySum + (sdata[tid +  64]); } __syncthreads(); }

    if( tid < 32 )
    {
        __syncthreads(); //  <=== Extra __syncthreads(), breaks reduction!

        // Synchronize within warp using volatile type
        volatile SumType *smem = sdata;
        if( blockSize >= 64 ) { smem[tid] = mySum = mySum + (smem[tid + 32]); }
        if( blockSize >= 32 ) { smem[tid] = mySum = mySum + (smem[tid + 16]); }
        if( blockSize >= 16 ) { smem[tid] = mySum = mySum + (smem[tid +  8]); }
        if( blockSize >=  8 ) { smem[tid] = mySum = mySum + (smem[tid +  4]); }
        if( blockSize >=  4 ) { smem[tid] = mySum = mySum + (smem[tid +  2]); }
        if( blockSize >=  2 ) { smem[tid] = mySum = mySum + (smem[tid +  1]); }
    }
}

为什么插入额外的__syncthreads()导致此代码不再起作用?

请参阅下面的答案,了解自包含的代码示例。

编辑:将__syncthreads()移动到示例中的if()语句中,以反映实际触发错误的代码。

1 个答案:

答案 0 :(得分:2)

问题与只为块中的某些线程调用__syncthreads()有关。最终结果是一些非常奇怪的行为。 来自CUDA C Programming Guide,B.6节:

  

__ syncthreads()在条件代码中是允许的,但仅当条件在整个线程块中进行相同的求值时,   否则代码执行可能会挂起或产生意外   副作用。

我将其归结为以下简单示例。共享内存中的标志s_onlyOneBlock由每个块中的一个线程设置;在块0中,它是真的,而在其他块中,它是错误的。可以预期块0中的所有线程获得s_onlyOneBlock = true;但是,因为__syncthreads()只调用线程0到31,所以行为是意外的:只有线程0到31才能获得s_onlyOneBlock = true:

#include <stdio.h>

static __global__ void
kernel()
{
    __shared__ bool s_onlyOneBlock;
    const unsigned int tid = threadIdx.x;

    // Call __syncthreads() for only some threads (don't do this!)
    if( tid < 32 )
        __syncthreads();

    // Thread 0 sets s_onlyOneBlock
    if( tid == 0 )
        s_onlyOneBlock = ( blockIdx.x == 0 );

    __syncthreads();

    if( s_onlyOneBlock )
    {
        // Only block 0 should reach this point
        if( tid==0 || tid==31 || tid==32 || tid==128 )
            printf("s_onlyOneBlock is TRUE:  block=%d thread=%d\n", blockIdx.x, threadIdx.x);
    }
    else
    {
        if( tid==0 || tid==31 || tid==32 || tid==128 )
            printf("s_onlyOneBlock is false: block=%d thread=%d\n", blockIdx.x, threadIdx.x);
    }
}

int main()
{
    kernel<<<2, 256>>>();
    cudaDeviceSynchronize();
}

结果:

nvcc syncproblem.cu -o syncproblem
./syncproblem 
s_onlyOneBlock is false: block=0 thread=128  <--- should be true!
s_onlyOneBlock is false: block=1 thread=128
s_onlyOneBlock is false: block=0 thread=32   <--- should be true!
s_onlyOneBlock is false: block=1 thread=32
s_onlyOneBlock is TRUE:  block=0 thread=0
s_onlyOneBlock is TRUE:  block=0 thread=31
s_onlyOneBlock is false: block=1 thread=0
s_onlyOneBlock is false: block=1 thread=31