了解CUDA序列化和重新收敛点

时间:2014-11-05 21:54:49

标签: serialization cuda

编辑:我意识到,遗憾的是,我在第一个示例代码中的while语句末尾忽略了一个分号,并自行误解了它。因此,对于具有threadIdx.x != s的线程,事实上存在一个空循环,该循环之后是一个收敛点,并且此时所有其他线程的线程都在等待,而不会增加s变量。对于对此感兴趣的人,我将在下面留下原始(未经修正)的问题。请注意,第一个示例中第二行末尾缺少分号,因此s++与循环体没有任何共同之处。

-

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

__shared__ int s = 0;
while (s != threadIdx.x)
    s++; // serialized code

最终会出现HW死锁,因为nvcc编译器会在while (s != threadIdx.x)s++语句之间放置重新收敛点。如果我理解正确,这意味着一旦线程达到重新收敛点,该线程就会停止执行并等待其他线程,直到它们到达该点。但是,在这个例子中,这种情况永远不会发生,因为线程#0进入while循环的主体,到达重新收敛点而不增加s变量,其他线程陷入无限循环。

工作解决方案应如下:

__shared__ int s = 0;
while (s < blockDim.x)
    if (threadIdx.x == s)
        s++; // serialized code

这里,块中的所有线程都进入循环体,所有线程都评估条件,只有线程#0在第一次迭代中递增s变量(并且循环继续)。

我的问题是,为什么第二个例子在第一个例子挂起时有用?更具体地说,if语句只是另一个分歧点,就汇编语言而言,应该编译成与循环中的条件相同的条件跳转指令。那么为什么在第二个例子中s++之前没有任何重新收敛点,并且事实上它在声明之后立即消失了?

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

谢谢。

2 个答案:

答案 0 :(得分:1)

将重新收敛点放在while (s != threadIdx.x)s++;的调用之间是没有意义的。它会破坏程序流,因为编译时所有线程都应该可以访问一段代码的重新收敛点。下图显示了第一段代码的流程图以及可能和不可能的重新收敛点。

enter image description here

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

__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二进制工具输出来观察CUDA程序集的结果。结果是:

enter image description here

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

总的来说,目前还不清楚你要用这段代码实现什么。同样在第二段代码中,重新收敛点应该在while循环代码块之后再次出现(我不是指whileif之间)。

答案 1 :(得分:1)

它“挂起”的原因既不是HW死锁也不是分支,至少不是直接的。您为一个或多个线程生成无限循环(已经怀疑)。

在您的示例中,没有真正的收敛点。由于您不使用任何同步,因此没有任何实际等待的线程。 while循环在这里发生的事情几乎是忙碌的等待。 如果所有线程都返回,则内核仅完成。由于你有一个(或多个)无限循环(偶然可能甚至没有 - 但这不太可能),内核永远不会完成。

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

  1. 列表项
  2. 线程5循环并检查s变为5
  3. s是4
  4. 两个线程递增s,它变为6
  5. 同时,线程5只到达其循环的末尾。
  6. 现在它到达下一个循环迭代并检查s并且它不是5。
  7. 线程5将永远无法完成,因为您通过==检查并且s的值已经超过了线程ID的值。
  8. 你的解决方案也很混乱,因为每个线程连续执行序列化代码(毕竟这可能是意图 - 尽管实际上很奇怪):

    1. 线程0将执行序列化代码
    2. 之后,线程1将执行序列化代码
    3. 大多数示例显示了一个程序,其中每个线程在某些代码上工作,然后所有线程都被同步,只有单个线程执行更多代码(可能需要所有线程的结果)。 所以,你的第二个例子“有效”,因为没有线程被卡在无限循环中,但我无法想到为什么有人会使用这样的代码, 因为它令人困惑,而且根本不平行。