【问题标题】:cuda: warp divergence overhead vs extra arithmeticcuda:扭曲发散开销与额外算术
【发布时间】:2013-05-20 07:22:53
【问题描述】:

当然,通过ifswitch 语句,在 GPU 上要不惜一切代价避免扭曲发散。

但是,warp 发散(仅调度 一些 线程以执行某些行)与额外无用算术的开销是多少?

考虑以下虚拟示例:

版本 1:

__device__ int get_D (int A, int B, int C)
{
    //The value A is potentially different for every thread.

    int D = 0;

    if (A < 10)
        D = A*6;
    else if (A < 17)
        D = A*6 + B*2;
    else if (A < 26)
        D = A*6 + B*2 + C; 
    else 
        D = A*6 + B*2 + C*3;

    return D;
}

对比

版本 2:

__device__ int get_D (int A, int B, int C)
{
    //The value A is potentially different for every thread.

    return  A*6 + (A >= 10)*(B*2) + (A < 26)*C + (A >= 26)*(C*3);
}

我的真实场景更复杂(更多条件)但想法相同。

问题:

warp 分歧的开销(在调度中)是否如此之大以至于版本 1)比版本 2 慢?

与版本 1 相比,版本 2 需要更多的 ALU,其中大部分都浪费在“乘以 0”上(只有少数几个条件计算结果为 1 而不是 0)。这是否会将有价值的 ALU 捆绑在无用的操作中,从而延迟其他 warp 中的指令?

【问题讨论】:

  • 用if(A
  • 禁用扭曲中的某些线程不允许其他线程占用这些核心,除非整个扭曲被禁用。线程指令只能在一个经线的基础上执行。未使用的曲速通道在其他地方没有任何好处

标签: cuda gpu warp-scheduler


【解决方案1】:

此类问题的具体答案通常很难提供。影响两例比较分析的因素有很多:

  • 您说每个线程的 A 可能不同,但真实程度实际上会影响比较。
  • 总体而言,您的代码是受计算约束还是受带宽约束肯定会影响答案。 (如果您的代码受带宽限制,那么这两种情况可能没有性能差异)。
  • 我知道您已将 A、B、C 识别为整数,但看似无害的更改(例如将它们设为 float)可能会显着影响答案。

幸运的是,有一些分析工具可以帮助给出清晰、具体的答案(或者可能表明这两个案例之间没有太大区别。)您已经很好地确定了您关心的 2 个具体案例。为什么不对 2 进行基准测试?如果您想更深入地挖掘,分析工具可以提供有关指令重放的统计信息(由于扭曲发散而产生)带宽/计算绑定指标等。

我必须反对这个笼统的声明:

当然,在 GPU 上要不惜一切代价避免通过 if 和 switch 语句产生的 warp 分歧。

这根本不是真的。机器处理不同控制流的能力实际上是一个特性,它允许我们用更友好的语言(如 C/C++)对其进行编程,并且实际上将它与其他一些不支持的加速技术区分开来为程序员提供这种灵活性。

与任何其他优化工作一样,您应该首先将注意力集中在繁重的工作上。您提供的这段代码是否构成了您的应用程序完成的大部分工作?在大多数情况下,将这种级别的分析工作投入到基本上是胶水代码或不属于应用程序主要工作的内容中是没有意义的。

如果这是您代码的大部分工作,那么分析工具确实是一种强大的方法,可以获得有意义的好答案,这可能比尝试进行学术分析更有用。

现在我来回答你的问题:

warp 分歧的开销(在调度中)是否如此之大以至于版本 1)比版本 2 慢?

这将取决于实际发生的特定分支级别。在最坏的情况下,对于 32 个线程具有完全独立的路径,机器将完全序列化并且您实际上以 1/32 的峰值性能运行。线程的二叉决策树类型细分不会产生这种最坏的情况,但肯定可以在树的末端接近它。由于最后的完全线程分歧,可能会观察到此代码超过 50% 的减速,可能是 80% 或更高的减速。但这在统计上取决于实际发生分歧的频率(即它取决于数据)。在最坏的情况下,我希望第 2 版更快。

与版本 1 相比,版本 2 需要更多的 ALU,其中大部分都浪费在“乘以 0”上(只有少数几个条件计算结果为 1 而不是 0)。这是否会将有价值的 ALU 捆绑在无用的操作中,从而延迟其他 warp 中的指令?

floatint 在这里可能会有所帮助,也许你可以考虑探索一下。但是第二种情况(对我而言)似乎与第一种情况具有所有相同的比较,但有一些额外的乘法。在浮点情况下,机器可以在每个时钟的每个线程中进行一次乘法运算,因此速度非常快。在 int 情况下,它更慢,您可以看到具体的指令吞吐量取决于架构here。我不会过分担心那种程度的算术。同样,如果您的应用受内存带宽限制,根本没有区别

解决所有这些问题的另一种方法是编写内核来比较感兴趣的代码,编译为 ptx (nvcc -ptx ...) 并比较 ptx 指令。这可以更好地了解机器线程代码在每种情况下的外观,如果您只执行指令计数之类的操作,您可能会发现两种情况之间没有太大区别(在这种情况下应该支持选项 2) .

【讨论】:

  • 这个函数是算法的基本主力的一部分。它将被调用数百万次。这就是为什么我如此关心这些小细节。这些值实际上是int,而不是float。您是否建议将它们更改为 float 实际上可能会更好?
  • 这个函数用于获取数组的索引,所以我必须将float 转换回int,这让我担心由于舍入错误...
  • 不,您不想在 int 和 float 之间来回转换。如果它是一个基本的主力,你对这两种选择都有一个非常简洁的描述,你最好的选择是基准/配置文件和比较。带宽与计算限制等问题尚未得到解答,但很重要。
  • 对不起,我不熟悉带宽的概念。上述数组存储在__常量__内存缓存中。我的问题中的函数计算访问该数组的索引。
  • 您可能对网络研讨会“CUDA 优化:Paulius Micikevicius 博士的识别性能限制因素”here 感兴趣
猜你喜欢
  • 2014-12-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-02-16
  • 2011-08-08
  • 1970-01-01
  • 2014-07-09
相关资源
最近更新 更多