Serialization 了解CUDA序列化和重新聚合点

Serialization 了解CUDA序列化和重新聚合点,serialization,cuda,Serialization,Cuda,EDIT:我意识到,不幸的是,我在第一个示例代码中的语句中忽略了末尾的分号,并自己对其进行了错误解释。因此,对于threadIdx.x!=s,循环之后的聚合点,以及在此点等待所有其他线程的线程,而不增加s变量。我将把下面的原始(未更正)问题留给对它感兴趣的任何人。请注意,在第一个示例中,第二行末尾缺少分号,因此,s++与循环体没有任何共同之处 -- 我们在CUDA课程中学习序列化,老师告诉我们这样的代码: __shared__ int s = 0; while (s != threadIdx.x

EDIT:我意识到,不幸的是,我在第一个示例代码中的
语句中忽略了
末尾的分号,并自己对其进行了错误解释。因此,对于
threadIdx.x!=s
,循环之后的聚合点,以及在此点等待所有其他线程的线程,而不增加
s
变量。我将把下面的原始(未更正)问题留给对它感兴趣的任何人。请注意,在第一个示例中,第二行末尾缺少分号,因此,
s++
与循环体没有任何共同之处

--

我们在CUDA课程中学习序列化,老师告诉我们这样的代码:

__shared__ int s = 0;
while (s != threadIdx.x)
    s++; // serialized code
将以硬件死锁结束,因为nvcc编译器在
while(s!=threadIdx.x)
s++
语句之间放置了一个重新聚合点。如果我理解正确,这意味着一旦一个线程到达重新聚合点,该线程就会停止执行,并等待其他线程也到达该点。但是,在本例中,这永远不会发生,因为线程#0进入while循环体,在不增加
s
变量的情况下到达重新聚合点,而其他线程会陷入无休止的循环中

工作解决方案应如下所示:

__shared__ int s = 0;
while (s < blockDim.x)
    if (threadIdx.x == s)
        s++; // serialized code
\uuuu共享\uuuu int s=0;
而(s
在这里,一个块中的所有线程都进入循环体,所有线程都计算条件,只有线程0在第一次迭代中增加
s
变量(循环继续)

我的问题是,如果第一个挂起,为什么第二个示例有效?更具体地说,
if
语句只是另一个分歧点,就汇编语言而言,应该编译成与循环中的条件相同的条件跳转指令。那么,为什么在第二个示例中,在
s++
之前没有任何重新聚合点,并且它实际上是在语句之后立即出现的呢

在其他来源中,我只发现发散代码是为每个分支独立计算的-例如在
if/else
语句中,首先计算
if
分支,所有
else
分支线程在同一个扭曲内屏蔽,然后其他线程在第一次等待时计算
else
分支。在if/else语句之后有一个重新聚合点。那么,为什么第一个示例冻结,而没有将循环拆分为两个分支(一个线程的
true
分支和一个warp中所有其他线程的等待
false
分支)


谢谢。

在调用
而(s!=threadIdx.x)
s++之间设置重新聚合点没有意义。它会中断程序流,因为一段代码的重新聚合点应该可以在编译时被所有线程访问。下图显示了第一段代码的流程图以及可能和不可能的重新聚合点

关于通过
SSY
指令记录收敛点,我创建了下面的简单内核,类似于您的第一段代码

__global__ void kernel_1() {
    __shared__ int s;
    if(threadIdx.x==0)
        s = 0;
    __syncthreads();
    while (s == threadIdx.x)
        s++; // serialized code
}
并用-O3将其编译为CC=3.5。以下是使用
cuobjdum
binary工具进行输出以观察CUDA组件的结果。结果是:

我不是CUDA汇编的阅读专家,但我可以在
0038
00a0
中查看
。在第
00a8
行,如果它满足
while
循环条件并再次执行代码块,它将分支到
0x80
。重新汇聚点的引入位于第
0058行
引入第
0xb8行
作为重新汇聚点,该点位于出口附近的环路条件检查之后

总的来说,不清楚您使用这段代码想要实现什么。同样在第二段代码中,重新聚合点应该再次位于
while
循环代码块之后(我不是指
while
if
)。

它“挂起”的原因既不是硬件死锁,也不是分支,至少不是直接的。您为一个或多个线程生成了一个无止境的循环(正如已经怀疑的那样)

在您的示例中,实际上不存在收敛点。由于不使用任何同步,因此没有任何线程实际等待。while循环在这里发生的事情几乎是一个繁忙的等待。 只有当所有线程返回时,内核才会完成。由于您有一个(或多个)无止境的循环(偶然可能甚至没有循环-然而这不太可能),内核将永远不会完成

您声明了一个共享变量s。块中的所有线程都知道此变量。 在while语句中,您基本上说(对每个线程):递增s,直到它达到(本地)线程id的值。由于所有线程都并行递增s,因此引入了竞争条件。 例如:

  • 列表项
  • 线程5正在循环并检查s是否变为5
  • s是4
  • 两个线程增加s,它变为6
  • 同时,线程5只到达其循环的末尾
  • 现在它进入下一个循环迭代并检查s,而不是5
  • 线程5将永远无法完成,因为您检查via==并且s的值已经超过了线程id的值
  • 另外,您的解决方案相当混乱,因为每个线程都连续执行序列化代码(这可能是最终的意图——尽管这实际上很奇怪):

  • 线程0将执行序列化代码
  • 之后,线程1将执行序列化代码
  • 等等
  • 大多数例子