CUDA:if语句中的__syncthreads()

时间:2012-09-20 19:27:54

标签: c parallel-processing cuda synchronizing

我有一个关于CUDA同步的问题。特别是,我需要对if语句中的同步进行一些澄清。我的意思是,如果我将__syncthreads()放在if语句的范围内,该块语句被块内的一小部分线程击中,会发生什么?我认为有些线程将“永远”等待其他不会达到同步点的线程。所以,我编写并执行了一些示例代码来检查:

__global__ void kernel(float* vett, int n)
{
    int index = blockIdx.x*blockDim.x + threadIdx.x;
    int gridSize = blockDim.x*gridDim.x;

    while( index < n )
    {   
        vett[index] = 2;
        if(threadIdx.x < 10)
        {
            vett[index] = 100;
            __syncthreads();
        }
        __syncthreads();

        index += gridSize;
    }
}

令人惊讶的是,我观察到输出非常“正常”(64个元素,块大小为32):

100 100 100 100 100 100 100 100 100 100 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
100 100 100 100 100 100 100 100 100 100 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2

所以我通过以下方式稍微修改了我的代码:

__global__ void kernel(float* vett, int n)
{
    int index = blockIdx.x*blockDim.x + threadIdx.x;
    int gridSize = blockDim.x*gridDim.x;

    while( index < n )
    {   
        vett[index] = 2;
        if(threadIdx.x < 10)
        {
            vett[index] = 100;
            __syncthreads();
        }
        __syncthreads();
            vett[index] = 3;
        __syncthreads();

        index += gridSize;
    }
}

输出结果为:

3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 
3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 

同样,我错了:我认为在修改向量元素之后,if语句中的线程将保持等待状态,并且永远不会超出if范围。 那么......你能澄清一下发生了什么吗?在同步点之后获取的线程是否会阻塞在屏障处等待的线程? 如果您需要重现我的情况,我使用CUDA Toolkit 5.0 RC和SDK 4.2。非常感谢。

4 个答案:

答案 0 :(得分:16)

简而言之,行为是 undefined 。所以它有时可能会做你想要的,或者它可能没有,或者(很可能)只会挂起或崩溃你的内核。

如果你真的很好奇内部的工作方式,你需要记住线程不是独立执行,而是一次一个warp(一组32个线程)。

这当然会产生条件分支的问题,其中条件不会在整个warp中统一评估。这个问题是通过一个接一个地执行两个路径来解决的,每个路径都禁用那些不应该执行该路径的线程。 IIRC在现有硬件上首先采用分支,然后在不采用分支的情况下执行路径,但此行为未定义,因此无法保证。

这种单独的路径执行一直持续到某一点,编译器可以确定两个独立执行路径的所有线程(“重新收敛点”或“同步点”)都可以保证它。当第一个代码路径的执行到达这一点时,它将被停止,而第二个代码路径则被执行。当第二条路径到达同步点时,所有线程再次启用,并从那里统一继续执行。

如果在同步之前遇到另一个条件分支,情况会变得更复杂。这个问题通过一堆仍然需要执行的路径来解决(幸运的是,堆栈的增长是有限的,因为我们一个warp最多可以有32个不同的代码路径)。

插入同步点的位置未定义,甚至在架构之间略有不同,因此无法保证。您将从Nvidia获得的唯一(非官方)评论是编译器非常擅长找到最佳同步点。然而,通常存在微妙的问题,可能会使最佳点进一步下降,尤其是如果线程提前退出。

现在要了解__syncthreads()指令的行为(转换为PTX中的bar.sync指令),重要的是要意识到每个线程不执行该指令,而是立即执行整个warp (无论是否禁用任何线程),因为只需要同步块的warp。 warp的线程已经同步执行,并且进一步同步将无效(如果所有线程都已启用)或在尝试同步来自不同条件代码路径的线程时导致死锁。

您可以按照此描述的方式使用您的特定代码行为。但请记住,所有这些都是未定义,没有任何保证,并且依赖于特定行为可能会在任何时候破坏您的代码。

您可能需要查看PTX manual以获取更多详细信息,尤其是__syncthreads()编译到的bar.sync指令。下面由艾哈迈德引用的Henry Wong的"Demystifying GPU Microarchitecture through Microbenchmarking" paper也非常值得一读。即使对于现在过时的体系结构和CUDA版本,有关条件分支和__syncthreads()的部分似乎仍然普遍有效。

答案 1 :(得分:5)

CUDA模型是MIMD,但当前的NVIDIA GPU以warp粒度而不是线程实现__syncthreads()。这意味着,这些warps inside a thread-block的同步不一定是threads inside a thread-block__syncthreds()等待线程块的所有“warp”触及屏障或退出程序。有关详细信息,请参阅Henry Wong's Demistifying paper

答案 2 :(得分:1)

__ syncthreads()用于同步块内的线程。这意味着块中的所有线程将等待所有线程完成,然后继续。

考虑一个块中有一些线程的情况,这些线程进入if语句而有些则没有。那些等待的线程将被阻止;永远等待。

通常,在if条件语句中放置同步并不是一种好的方式。最好避免它,如果你有它,重新设计你的代码。 同步的目的是确保所有线程一起进行,为什么首先使用if-statement过滤掉它们?

要添加,如果跨块需要同步。 需要重新启动内核。

答案 3 :(得分:0)

最好在if条件中避免__syncthreads()。您可以使用for循环和__syncthreads()重写for循环代码。

相关问题