CUDA:if语句中的__syncthreads()

我有一个关于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。 非常感谢提前。

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

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

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

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

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

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

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

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

您可能需要查看PTX手册以获取更多详细信息,尤其是__syncthreads()编译的bar.sync指令。 Henry Wong的“通过Microbenchmarking揭开GPU微架构”的论文 ,下面由ahmad引用,也值得一读。 即使对于现在过时的体系结构和CUDA版本,关于条件分支和__syncthreads()似乎仍然普遍有效。

CUDA模型是MIMD,但是当前的NVIDIA GPU以warp粒度而不是线程实现__syncthreads() 。 这意味着,这些是warps inside a thread-block ,它们不一定threads inside a thread-block__syncthreds()等待thread-block的所有’warp’命中屏障或退出程序。 有关详细信息,请参阅Henry Wong的Demistifying文章 。

除非始终在一个线程块内的所有线程中到达该语句,否则不得使用__syncthreads() 。 从编程指南 (B.6):

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

基本上,您的代码不是一个结构良好的CUDA程序。

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

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

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

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