HLSL 分支规避
Posted
技术标签:
【中文标题】HLSL 分支规避【英文标题】:HLSL branch avoidance 【发布时间】:2012-09-17 13:47:54 【问题描述】:我有一个着色器,我想在其中移动顶点着色器中的一半顶点。我正在尝试从性能的角度来确定执行此操作的最佳方法,因为我们正在处理超过 100,000 个顶点,因此速度至关重要。我查看了 3 种不同的方法:(伪代码,但足以给你这个想法。<complex formula>
我不能给出,但我可以说它涉及一个 sin()
函数,以及一个函数调用(只是返回一个数字,但仍然是函数调用),以及一堆关于浮点数的基本算术)。
if (y < 0.5)
x += <complex formula>;
这样做的好处是<complex formula>
只执行了一半,但坏处是肯定会导致分支,实际上可能比公式慢。它是最易读的,但在这种情况下,我们更关心速度而不是可读性。
x += step(y, 0.5) * <complex formula>;
使用 HLSL 的 step() 函数(如果第一个参数大于则返回 0,如果小于则返回 1),您可以消除分支,但现在每次都调用 <complex formula>
,其结果乘以0(因此浪费了精力)一半的时间。
x += (y < 0.5) ? <complex formula> : 0;
这个我不知道。 ?:
是否会导致分支?如果没有,是对等式的两边进行评估,还是只对相关的一侧进行评估?
最后一种可能性是<complex formula>
可能会被卸载回 CPU 而不是 GPU,但我担心它在计算 sin() 和其他操作时会变慢,这可能会导致净损失。此外,这意味着必须将另一个数字传递给着色器,这也可能导致开销。任何人都知道哪个是最好的行动方案?
附录:
根据http://msdn.microsoft.com/en-us/library/windows/desktop/bb509665%28v=vs.85%29.aspx
step()
函数在内部使用?:
,因此它可能并不比我的第三种解决方案好,而且可能更糟,因为每次肯定都会调用<complex formula>
,而它可能只是有一半时间用直号?:
调用。 (还没有人回答这部分问题。)虽然避免两者并使用:
x += (1.0 - y) * <complex formula>;
可能比他们中的任何一个都好,因为在任何地方都没有进行比较。 (并且y
始终为 0 或 1。)仍有一半时间不必要地执行 <complex formula>
,但完全避免分支可能是值得的。
【问题讨论】:
该方法取决于目标硬件。您可以比较这些变体的汇编代码(例如,RenderMonkey
可以分析 Radeon 卡的性能)。另外,顶点着色器是瓶颈吗?也许所有变体都会给出相同的结果:)
某些内核上的着色器必须同步执行,所以无论如何complex formula
都会被评估。
另外,如果y
可以计算为网格的函数,也许您只需拆分网格或场景几何体并运行两个不同的着色器。
目标平台是 XBox360、PS3 和 PC。 (PC 版本仅用于测试,因此不太重要。通常,它使用与 PS3 相同的代码。)知道y
将始终恰好为 0 或 1,两者之间没有任何关系,这可能会有所帮助。 (这是四边形的纹理坐标。)我想step()
函数可以简单地替换为(1.0 - y)
并具有相同的效果。仍然导致公式计算出严格必要的两倍...
哦,如上所述,两个不同的着色器不是一个选项,因为每个多边形都会跨越阈值。它只是渲染了很多四边形。把它想象成粒子效果。不完全是这样,但这是我认为在不违反 NDA 的情况下可以安全达到的接近程度。
【参考方案1】:
也许看看this answer。
我的猜测(这是一个性能问题:衡量它!)你最好不要保留if
声明。
原因一:理论上(如果调用正确),着色器编译器应该足够聪明,以便在编译您的 @987654325 时在分支指令和类似于 step
函数的东西之间做出最佳选择@ 陈述。改进它的唯一方法是分析[1]。请注意,在这种粒度级别上,它可能取决于硬件。
[1] 或者,如果您对数据的布局方式有专门的了解,请继续阅读...
第二个原因是着色器单元的工作方式:即使单元中的一个片段或顶点采用与其他分支不同的分支,则着色器单元必须采用两个分支。但如果他们都采用相同的分支 - 另一个分支将被忽略。因此,虽然它是按单元计算的,而不是按顶点计算的 - 仍然有可能跳过昂贵的分支。
对于片段,着色器单元具有屏幕上的局部性 - 这意味着您可以在附近像素组都采用相同分支的情况下获得最佳性能(请参阅我的 linked answer 中的插图)。老实说,我不知道如何将顶点分组为单元 - 但如果您的数据被适当地分组 - 您应该获得所需的性能优势。
最后:值得指出的是,您的 <complex formula>
- 如果您说您可以手动将其从 HLSL 中提升出来 - 它很可能会被提升到基于 CPU 的预着色器中(至少在 PC 上) ,根据记忆Xbox 360不支持这个,不知道PS3)。您可以通过反编译着色器来检查这一点。如果您只需要每次绘制(而不是每个顶点/片段)计算一次,那么在 CPU 上执行它可能性能最佳。
【讨论】:
幸运的是,我没有在这里处理碎片着色器,所以这不是问题。对于顶点,它可能遵循 00110011 模式 - 每个四边形的顶部 2 顶点和底部 2 顶点。我很确定所有四边形都在一个绘制调用中绘制,但顶点移动是基于每个四边形的。老实说,PC上发生的事情并不重要,因为那不是可交付的。这里的其他人说if
很糟糕,应该不惜一切代价避免,但我想在我们得到这个分析之前我不能确定。它很可能会转移到 CPU,但这有点棘手......
Xbox 中的数字:控制(无顶点移动):830K GPU 周期。方法 #1 (if
):834K 周期。方法 #2 (step()
):836K 周期。方法 #3 (?:
):835K 周期。方法 #4 (1-y
):844K 周期。我真的很惊讶最后一个速度最慢,因为它是唯一一个没有分支的。但是您对if
的看法是正确的——至少在这种情况下是这样。不过,有人告诉我 PS3 将是另一个故事。我们会看看情况如何。
哎呀-等一下。我用不同的摄像机角度重新进行了测试(远处的镜头显示了更多的四边形,但距离更远),结果被颠倒过来了。对照:566K,方法#1:595K,方法#2:590K,方法#3:591K,方法#4:611K。 1-y
出人意料地仍然是最差的,但其他 3 个完全相反。这可能是一个更典型的视角,所以我必须将step()
作为这种情况下的最佳解决方案投我的票,尽管?:
在这两种情况下都非常接近。陪审团仍在 PS3 上。【参考方案2】:
我厌倦了忽略我的条件,所以我只创建了另一个内核并在 c 执行中进行了覆盖。 如果您需要它始终保持准确,我建议您进行此修复。
【讨论】:
不确定这有什么关系?另外,这个问题是 3 年和 2 份工作前的问题,所以我什至无法访问有问题的代码...... 只是把它放在那里。 您是说您将逻辑移至 GPU 上的计算内核吗?或者您可以访问执行着色器的现有 GPU 内核的源代码并对其进行修改?学习如何做的建议链接? 抱歉有一段时间了,但我自己解决了问题并将逻辑移至 GPU。您只需创建一个新的线性内核(由 thread.x 标识),您可以通过控制和计算之间的缓冲区传输来完成此操作。这允许额外的验证/记录。以上是关于HLSL 分支规避的主要内容,如果未能解决你的问题,请参考以下文章
IntelliJ IDEA SVN 分支代码合并(图文详解)