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

Posted

技术标签:

【中文标题】条件语句会减慢着色器的速度吗?【英文标题】:Do conditional statements slow down shaders? 【发布时间】:2016-10-16 02:00:09 【问题描述】:

我想知道着色器(顶点/片段/像素...)中的“if 语句”是否真的会降低着色器的性能。例如:

用这个更好吗:

vec3 output;
output = input*enable + input2*(1-enable);

而不是使用这个:

vec3 output;
if(enable == 1)

    output = input;

else

    output = input2;

在另一个论坛上有一个讨论(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-(-)

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

编辑:

这种情况是怎么回事,这里假设 enable 是一个统一变量,它总是设置为 0:

if(enable == 1) //never happens

    output = vec4(0,0,0,0);

else  //always happens

    output = calcPhong(normal, lightDir);

我认为我们在着色器内部有一个分支,它会减慢着色器的速度。对吗?

制作 2 个不同的着色器是否更有意义,比如一个用于 else 部分,另一个用于 if 部分?

【问题讨论】:

我相信 if 语句只是减少了着色器需要完成的计算。第一个示例强制着色器计算这两种情况......而 if 语句减少它。不,您的 if 语句不会消耗处理时间....因为它在编译后在最后的汇编代码中作为分支出现 感谢您的快速答复!我编辑了这个问题。你能回答一下编辑过的部分吗?谢谢!!! 我认为你不应该有 2 个着色器。我相信这开始进入过早的优化。正如@246tNt 指出的那样,如果它是统一的,编译器将处理它。但除此之外,我真的不认为你应该这么担心性能。先把它做好。然后如果看到着色器是瓶颈,然后查看算法。像这样的东西应该不会有太大伤害 我只问了这个问题,因为我想知道我是否可以通过这个获得更好的性能。如果您使用着色器进行迭代计算,并且在渲染图片之前必须运行 100 次,那么每毫秒都很重要 除非您根据经验分析并确定这是一个瓶颈,否则始终使用具有最明确意图的代码。在这种情况下,if 语句。造成这种情况的原因有两个:您最终不会得到没有证据支持它们更好的复杂代码结构,更重要的是,编译器更有可能识别您的意图并优化问题。跨度> 【参考方案1】:

着色器的什么原因甚至可能导致if 语句的性能问题?它与着色器的执行方式以及 GPU 从何处获得大量计算性能有关。

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

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

除非……它没有。

例如,如果条件是波前中的每个调用所采用的条件,则不需要运行时发散。因此,if 的成本只是检查条件的成本。

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

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

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

此外,即使一个条件被不同的波前采用,编译器也可以重构代码以不需要实际的分支。您举了一个很好的例子:output = input*enable + input2*(1-enable); 在功能上等同于if 语句。编译器可以检测到 if 正在用于设置变量,从而执行双方。这通常用于分支主体较小的动态条件的情况。

几乎所有硬件都可以处理var = bool ? val1 : val2,而不必分道扬镳。这在 2002 年是可能的。

由于这非常依赖于硬件,它...取决于硬件。但是,可以查看某些硬件时代:

桌面,D3D10 之前

那里,有点像狂野的西部。 NVIDIA 的此类硬件编译器因检测此类情况而臭名昭著,并且在您更改影响此类情况的制服时实际上重新编译您的着色器

总的来说,这个时代大约有 80% 的“从不使用if 语句”来自。但即使在这里,也不一定是真的。

您可以期待静态分支的优化。您可以希望静态统一的分支不会导致任何额外的减速(尽管 NVIDIA 认为重新编译会比执行它更快,这使得至少对于他们的硬件来说不太可能)。但是,即使所有调用都采用相同的分支,动态分支也会让您付出一些代价。

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

桌面,后 D3D10

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

桌面、D3D11+

这个时代的硬件几乎可以保证能够处理dynamically uniform 条件而几乎没有性能问题。事实上,它甚至不必是动态统一的。只要同一波前内的所有调用都采用相同的路径,您就不会看到任何显着的性能损失。

请注意,上一个时代的一些硬件可能也可以做到这一点。但这是几乎可以肯定是真的。

移动,ES 2.0

欢迎回到狂野的西部。虽然与 Pre-D3D10 桌面不同,这主要是由于 ES 2.0 口径硬件的巨大差异。可以处理 ES 2.0 的东西太多了,而且它们的工作方式也各不相同。

可能会优化静态分支。但是能否从静态统一分支中获得良好的性能是非常依赖于硬件的。

移动,ES 3.0+

这里的硬件比 ES 2.0 更加成熟和强大。因此,您可以期望静态统一的分支执行得相当好。并且某些硬件可能可以像现代桌面硬件那样处理动态分支。

【讨论】:

安全地假设编译器优化了内置条件函数,如 minstep,这样它们就不会发散,对吧? @Tenfour04 是的,它们要么使用专用指令,要么使用条件赋值指令,如上所述不会引起分歧。附带说明一下,我认为没有充分的理由使用 step 功能。你总是可以用更直观的条件赋值来替换它:step(a, b) * c -> (a >= b) ? c : 0 WebGL 怎么样?由于它基于 ES 2.0,它的工作方式是否与此类似,或者因为实现例如。在 Windows 上使用 ANGLE 在分支执行方面更成熟? @Andrew:“WebGL 怎么样?” 怎么样?我们谈论的是硬件的行为,而不是软件。界面不是那么重要。重要的是底层硬件如何反应。我只提到 API 版本,因为某些硬件支持的最高 API 版本是对其功能的粗略估计。【参考方案2】:

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

如果您的条件是统一的:不要打扰,让编译器处理它。 如果您的条件是动态的(例如从属性计算的值或从纹理或其他东西中获取的值),那么它会更复杂。

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

例如,如果某个分支在 99% 的情况下被采用并丢弃片段,那么您很可能希望保留条件。但是在上面的简单示例中,如果 enable 是一些动态条件,则算术选择可能会更好。

除非您有上述明确的案例,或者除非您针对一种固定的已知架构进行优化,否则最好让编译器为您解决这个问题。

【讨论】:

以上是关于条件语句会减慢着色器的速度吗?的主要内容,如果未能解决你的问题,请参考以下文章

未触发opengl片段着色器条件语句

如果 IF 语句不满足第一个条件,它会停止评估吗?

单一条件大幅减慢 SQL 查询速度

where条件放在子SQL语句中是否查询速度更快?

NOT IN 语句正在减慢我的查询速度

“python if”语句可以多条件判断吗?