我在互联网上看到很多关于分支差异的问题,以及如何避免它。然而,即使在阅读了几篇关于CUDA如何工作的文章之后,我似乎无法看到在大多数情况下如何避免分支差异。在有人伸出爪子向我跳起之前,请允许我描述一下我认为是“大多数情况”。
在我看来,大多数分支差异实例都涉及许多真正不同的代码块。例如,我们有以下情况:
if (A):
foo(A)
else:
bar(B)
如果我们有两个线程遇到这种分歧,线程1将首先执行,采用路径A.接下来,线程2将采用路径B.为了消除分歧,我们可能会将上面的块更改为这样读取:
foo(A)
bar(B)
假设在线程2上调用foo(A)
和在线程1上调用bar(B)
是安全的,可以预期性能会提高。但是,这就是我看到它的方式:
在第一种情况下,线程1和2串行执行。称这两个时钟周期。
在第二种情况下,线程1和2并行执行foo(A)
,然后并行执行bar(B)
。这仍然看起来像两个时钟周期,区别在于前一种情况,如果foo(A)
涉及从内存中读取,我想线程2可以在该延迟期间开始执行,这导致延迟隐藏。 如果是这种情况,分支发散代码会更快。
答案 0 :(得分:46)
你假设(至少它是你给出的例子和你做的唯一引用)避免分支差异的唯一方法是允许所有线程执行所有代码。
在这种情况下,我同意没有太大区别。
但是避免分支差异可能更多地与更高级别的算法重构相关,而不仅仅是添加或删除一些if语句,并使代码在所有线程中“安全”执行。
我将提供一个例子。假设我知道奇数线程需要处理像素的蓝色分量,甚至线程也需要处理绿色分量:
#define N 2 // number of pixel components
#define BLUE 0
#define GREEN 1
// pixel order: px0BL px0GR px1BL px1GR ...
if (threadIdx.x & 1) foo(pixel(N*threadIdx.x+BLUE));
else bar(pixel(N*threadIdx.x+GREEN));
这意味着每个备用线程都采用给定路径,无论是foo
还是bar
。所以现在我的warp执行时间要长两倍。
但是,如果我重新排列像素数据,以便颜色分量可能是32像素的块中连续的: BL0 BL1 BL2 ... GR0 GR1 GR2 ......
我可以编写类似的代码:
if (threadIdx.x & 32) foo(pixel(threadIdx.x));
else bar(pixel(threadIdx.x));
看起来我仍然有分歧的可能性。但由于分支发生在经线边界上,因此给定warp执行if
路径或else
路径,因此不会发生实际分歧。
这是一个简单的例子,可能很愚蠢,但它说明可能有办法解决扭曲分歧,而不涉及运行所有不同路径的所有代码。