条件语句会减慢着色器的速度吗?


74

我想知道着色器(顶点/片段/像素...)内部的“ if语句”是否真的减慢了着色器性能。例如:

最好使用这个:

而不是使用这个:

在另一个论坛上,有一个关于该话题的讨论(2013年):http : //answers.unity3d.com/questions/442688/shader-if-else-performance.html 在这里,人们说,If陈述确实很糟糕用于着色器的性能。

同样在这里,他们正在谈论if / else语句(2012)中的内容:https : //www.opengl.org/discussion_boards/showthread.php/177762-Performance-alternative-for-if-(-)

也许硬件或着色器编译器现在更好了,并且它们以某种方式解决了这个(也许不存在)性能问题。

编辑:

在这种情况下,可以说启用是一个统一变量,并且始终设置为0:

我认为这里在着色器内部有一个分支,可降低着色器的速度。那是对的吗?

制作2个不同的着色器(例如一个用于其他,另一个用于if部分)是否更有意义?


我相信if语句只会减少着色器需要完成的计算。第一个示例强制着色器计算两种情况……而if语句将其减少。不,您的if语句将不会消耗处理时间....因为它在编译后作为汇编代码的最后分支出现
Rakshith Ravi

感谢您的快速答复!我编辑了问题。您能否也回答编辑部分?谢谢!!!
汤玛斯(Thomas)

1
我不认为您应该有2个着色器。我相信这已经开始进入过早的优化。正如@ 246tNt所指出的,如果它是统一的,则编译器将对其进行处理。但是除此之外,我真的不认为您应该为此而担心。首先完成它。然后,如果看到着色器是瓶颈,则研究算法。像这样的东西应该不会受到太大伤害
Rakshith Ravi

我之所以只问这个问题,是因为我想知道我是否可以达到更好的性能。如果您使用着色器进行迭代计算,并且必须在渲染图片之前运行它100次,则每个毫秒都很重要
-Thomas

3
除非您凭经验分析并确定这是一个瓶颈,否则请始终使用意图最明确的代码。在这种情况下,if声明。这样做的原因有两个:最终,没有没有证据支持的卷积代码构造会更好,更重要的是,编译器更有可能识别出您的意图并优化问题。
丹·贝查德

Answers:


148

甚至有可能使着色器产生什么影响 if语句性能问题的是什么?它与着色器的执行方式以及GPU从何处获得其大量计算性能有关。

单独的着色器调用通常并行执行,同时执行相同的指令。他们只是在不同的输入值集上执行它们。他们共用制服,但内部寄存器不同。所有执行相同操作序列的一组着色器的一个术语是“波前”。

任何形式的条件分支的潜在问题是,它可能将所有问题搞砸了。它导致波前内的不同调用必须执行不同的代码序列。这是一个非常昂贵的过程,因此必须创建一个新的波前,将数据复制到该波前,等等。

除非...不是。

例如,如果条件是 波阵面中每次调用,则不需要运行时差异。因此,的成本if只是检查条件的成本。

因此,假设您有一个条件分支,并且假设波前中的所有调用都采用相同的分支。在这种情况下,表达式的性质存在三种可能性:

  • 编译时静态。条件表达式完全基于编译时常量。这样,您可以通过查看代码知道将采用哪个分支。几乎所有编译器都将其作为基本优化的一部分来处理。
  • 静态均匀分支。该条件基于涉及在编译时已知为常数(特别是常数和uniform值)的表达式。但是表达式的在编译时是未知的。因此,编译器可以静态确定波前不会被此破坏if,但是编译器无法知道将采用哪个分支。
  • 动态分支。条件表达式包含常数和统一数以外的术语。在这里,编译器无法先验地告知波阵面是否会破裂。是否需要发生取决于条件表达式的运行时评估。

不同的硬件可以处理不同的分支类型而不会产生分歧。

同样,即使条件是由不同的波前采取的,编译器也可以重组代码以不需要实际的分支。您给出了一个很好的示例:output = input*enable + input2*(1-enable);在功能上等同于该if语句。编译器可以检测到if正在使用an来设置变量,因此可以执行双方。经常在分支机构较小的动态条件下执行此操作。

几乎所有硬件都可以处理 var = bool ? val1 : val2而不必发散。这可能是在2002年。

由于这是非常依赖于硬件的,因此...取决于硬件。但是,可以观察到某些硬件时代:

D3D10之前的台式机

那里有点荒野。NVIDIA针对此类硬件的编译器臭名昭著,因为它可以检测到此类情况,并在您更改影响此类条件的制服时实际上重新编译着色器

通常,这个时代是大约80%的“永不使用if声明”的来源。但是即使在这里,也不一定是正确的。

您可以期望静态分支的优化。您可以希望,静态统一分支不会导致任何其他问题(尽管NVIDIA认为重新编译比执行速度更快的事实至少在他们的硬件上不太可能实现)。但是,即使所有调用都采用同一分支,动态分支也会使您付出一些代价。

这个时代的编译器会尽最大努力优化着色器,以便简单地执行简单条件。例如,您output = input*enable + input2*(1-enable);是一个不错的编译器可以从您的等效if语句中生成的东西。

台式机,D3D10后

这个时代的硬件通常能够以很小的速度处理静态统一的分支语句。对于动态分支,您可能会遇到减速,也可能不会遇到减速。

台式机,D3D11 +

这个时代的硬件几乎可以保证在几乎没有性能问题的情况下动态地处理统一条件。实际上,它甚至不必是动态统一的。只要同一波前的所有调用都采用相同的路径,就不会看到任何明显的性能损失。

请注意,上一个时期的某些硬件也可能会这样做。但这几乎可以肯定是真的。

移动版,ES 2.0

欢迎回到荒野的西部。尽管与Pre-D3D10台式机不同,这主要是由于ES 2.0口径硬件的巨大差异。可以处理ES 2.0的东西太多了,它们彼此之间的工作方式截然不同。

静态分支可能会得到优化。但是,是否从静态统一分支中获得良好的性能取决于硬件。

移动版,ES 3.0+

这里的硬件比ES 2.0更成熟,功能更强大。这样,您可以期望静态统一分支能够合理地执行。而且某些硬件可能可以像现代台式机硬件一样处理动态分支。


可以放心地认为编译器会优化诸如min和之step类的内置条件函数,以使它们不会发散,对吗?
Tenfour04年

3
@ Tenfour04是的,它们使用专用指令或条件赋值指令,如上所述,这不会引起差异。作为附带说明,我认为没有充分的理由使用步进功能。您总是可以用更直观的条件分配代替它:step(a, b) * c -> (a >= b) ? c : 0
Quinchilion,2016年

WebGL呢?由于它基于ES 2.0,它的工作原理是否与此类似,或者是因为其实现例如 在Windows上使用ANGLE关于分支执行是否更为成熟?
Andrew

2
@Andrew:“ WebGL怎么 ”怎么样?我们在谈论的是硬件而不是软件的行为。接口不是那么重要。重要的是底层硬件如何反应。我仅提及API版本,因为某些硬件上支持的最高API版本是其功能的粗略估计。
Nicol Bolas

11

它高度依赖于硬件和条件。

如果您的条件是统一的:不要打扰,让编译器处理它。如果您的条件是动态的(例如从属性计算的值或从纹理获取的值),则情况会更加复杂。

对于后一种情况,您几乎必须进行测试和基准测试,因为这取决于每个分支中代码的复杂性以及分支决策的“一致性”。

例如,如果分支之一占案件的99%,并且丢弃了该片段,那么很可能您希望保留该条件。但是OTOH在上面的简单示例中如果enable是一些动态条件,则算法选择可能会更好。

除非您拥有上述明确的案例,或者除非您针对一种固定的已知体系结构进行了优化,否则您可能最好选择编译器。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.