条件语句会减慢着色器的速度吗?
Do conditional statements slow down shaders?
我想知道 "if-statements" 内部着色器(顶点/片段/像素...)是否真的会降低着色器性能。例如:
用这个比较好:
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-(-)
也许硬件或 shadercompiler 现在更好了,他们以某种方式解决了这个(可能不存在的)性能问题。
编辑:
这种情况是怎么回事,这里假设 enable 是一个统一变量,它总是设置为 0:
if(enable == 1) //never happens
{
output = vec4(0,0,0,0);
}
else //always happens
{
output = calcPhong(normal, lightDir);
}
我认为我们在着色器中有一个分支可以减慢着色器的速度。对吗?
制作 2 个不同的着色器是否更有意义,一个用于 else,另一个用于 if 部分?
这在很大程度上取决于硬件和条件。
如果你的条件是统一的:别费心,让编译器来处理它。
如果你的条件是动态的(比如从属性计算的值或从纹理或其他东西中获取的值),那么它会更复杂。
对于后一种情况,您几乎必须进行测试和基准测试,因为这取决于每个分支中代码的复杂性以及 'consistent' 分支决策的方式。
例如,如果其中一个分支在 99% 的情况下被采用并丢弃片段,那么您很可能希望保留条件。但是如果 enable
是一些动态条件,OTOH 在上面的简单示例中,算术 select 可能会更好。
除非你有一个像上面那样的明确案例,或者除非你正在针对一种固定的已知架构进行优化,否则你最好让编译器为你解决这个问题。
着色器甚至可能造成 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
语句生成的东西。
台式机,Post-D3D10
这个时代的硬件通常能够处理静态统一的分支语句,几乎没有减速。对于动态分支,您可能会或可能不会遇到减速。
桌面,D3D11+
这个时代的硬件几乎可以保证能够处理 dynamically uniform 条件而几乎没有性能问题。实际上,它甚至不必是动态统一的;只要同一波前内的所有调用都采用相同的路径,您就不会看到任何显着的性能损失。
请注意,以前时代的某些硬件可能也可以做到这一点。但这是几乎可以肯定的。
移动版,ES 2.0
欢迎回到狂野的西部。虽然与 Pre-D3D10 桌面不同,但这主要是由于 ES 2.0 口径硬件的巨大差异。有大量的东西可以处理 ES 2.0,而且它们的工作方式各不相同。
静态分支可能会得到优化。但是你是否从静态统一分支中获得好的性能是非常依赖于硬件的。
手机,ES 3.0+
这里的硬件比 ES 2.0 更加成熟和强大。因此,您可以期望静态统一分支能够相当好地执行。有些硬件可能可以像现代桌面硬件那样处理动态分支。
我想知道 "if-statements" 内部着色器(顶点/片段/像素...)是否真的会降低着色器性能。例如:
用这个比较好:
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-(-)
也许硬件或 shadercompiler 现在更好了,他们以某种方式解决了这个(可能不存在的)性能问题。
编辑:
这种情况是怎么回事,这里假设 enable 是一个统一变量,它总是设置为 0:
if(enable == 1) //never happens
{
output = vec4(0,0,0,0);
}
else //always happens
{
output = calcPhong(normal, lightDir);
}
我认为我们在着色器中有一个分支可以减慢着色器的速度。对吗?
制作 2 个不同的着色器是否更有意义,一个用于 else,另一个用于 if 部分?
这在很大程度上取决于硬件和条件。
如果你的条件是统一的:别费心,让编译器来处理它。 如果你的条件是动态的(比如从属性计算的值或从纹理或其他东西中获取的值),那么它会更复杂。
对于后一种情况,您几乎必须进行测试和基准测试,因为这取决于每个分支中代码的复杂性以及 'consistent' 分支决策的方式。
例如,如果其中一个分支在 99% 的情况下被采用并丢弃片段,那么您很可能希望保留条件。但是如果 enable
是一些动态条件,OTOH 在上面的简单示例中,算术 select 可能会更好。
除非你有一个像上面那样的明确案例,或者除非你正在针对一种固定的已知架构进行优化,否则你最好让编译器为你解决这个问题。
着色器甚至可能造成 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
语句生成的东西。
台式机,Post-D3D10
这个时代的硬件通常能够处理静态统一的分支语句,几乎没有减速。对于动态分支,您可能会或可能不会遇到减速。
桌面,D3D11+
这个时代的硬件几乎可以保证能够处理 dynamically uniform 条件而几乎没有性能问题。实际上,它甚至不必是动态统一的;只要同一波前内的所有调用都采用相同的路径,您就不会看到任何显着的性能损失。
请注意,以前时代的某些硬件可能也可以做到这一点。但这是几乎可以肯定的。
移动版,ES 2.0
欢迎回到狂野的西部。虽然与 Pre-D3D10 桌面不同,但这主要是由于 ES 2.0 口径硬件的巨大差异。有大量的东西可以处理 ES 2.0,而且它们的工作方式各不相同。
静态分支可能会得到优化。但是你是否从静态统一分支中获得好的性能是非常依赖于硬件的。
手机,ES 3.0+
这里的硬件比 ES 2.0 更加成熟和强大。因此,您可以期望静态统一分支能够相当好地执行。有些硬件可能可以像现代桌面硬件那样处理动态分支。