避免在DirectX 10着色器中使用if语句?


14

我听说应该在着色器中避免if语句,因为语句的两个部分都将被执行,并且错误的语句将被丢弃(这会损害性能)。

在DirectX 10中仍然存在问题吗?有人告诉我,只有正确的分支才能执行。

对于该示例,我有以下代码:

float y1 = 5; float y2 = 6; float b1 = 2; float b2 = 3;

if(x>0.5){
    x = 10 * y1 + b1;
}else{
    x = 10 * y2 + b2;
}

还有其他方法可以使其更快吗?

如果是这样,该怎么办?

两个分支看起来都相似,唯一的区别是“常量”的值(y1, y2, b1, b2对于Pixel Shader中的所有像素都是相同的)。


1
老实说,这是非常过早的优化,只是在对代码进行基准测试并且着色器成为瓶颈的100%之前不要更改它们。
pwny 2012年

Answers:


17

微优化着色器的许多规则与带有矢量扩展的传统CPU的规则相同。这里有一些提示:

  • 内置测试功能(testlerp/ mix
  • 相加两个向量的成本与相加两个浮点数的成本相同
  • 免费打izz

的确,现代硬件上的分支比以前便宜,但如果可能的话,最好还是避免分支。通过使用毛刺和测试功能,您可以在不进行测试的情况下重写着色器:

/* y1, y2, b1, b2 */
float4 constants = float4(5, 6, 2, 3);

float2 tmp = 10 * constants.xy + constants.zw;
x = lerp(tmp[1], tmp[0], step(x, 0.5));

在两个值之间进行选择时,使用steplerp是非常常见的习惯用法。


6

通常没关系。着色器将以顶点或像素为一组执行(不同的供应商对此使用不同的术语,因此我避免这样做),如果一组中的所有顶点或像素采用相同的路径,则分支成本可以忽略不计。

您还需要信任着色器编译器。您编写的HLSL代码不应被视为将被编译为字节码或程序集的直接表示,并且编译器可以完全自由地将其转换为等效但避免分支的内容(例如,有时会出现lerp首选转换)。另一方面,如果编译器确定执行分支实际上是较快的路径,则它将编译为分支。在PIX或类似工具中查看生成的程序集会非常有帮助。

最后,这里仍然保留着古老的智慧-对它进行概要分析,确定它实际上是否对您来说是一个性能问题,然后再解决,而不是以前。假设某事可能是性能问题,那么按照该假设采取行动只会在以后带来更大问题的巨大风险。


4

引用罗伯特·鲁哈尼(Robert Rouhani)发布的链接/文章:

“条件代码(谓词)在较旧的体系结构中用于模拟真正的分支。编译到这些体系结构的if-then语句必须评估所有片段上的已采用和未采用分支指令。分支条件得到评估,并设置了条件代码。分支的每个部分中的指令必须在将结果写入寄存器之前检查条件码的值,结果,仅采取分支中的指令才将其输出写入,因此,在这些体系结构中,所有分支的成本都等于条件代码的两个部分。分支,再加上评估分支条件的成本。在这种架构上应谨慎使用分支。NVIDIAGeForce FX系列GPU在其片段处理器中使用条件代码分支仿真。”

如mh01所建议的那样(“在此处查看在PIX或类似工具中生成的程序集会很有帮助。”),您应该使用编译器工具来检查输出。以我的经验,nVidia的Cg工具(由于跨平台功能,Cg至今仍被广泛使用)完美地说明了GPU宝石条件代码(谓词)段落中提到的行为。因此,无论触发值如何,都将对每个分支基于片段进行评估,并且仅在最后将正确的分支放在输出注册表中。但是,浪费了计算时间。当时,我以为分支将有助于性能,尤其是因为所有该着色器中的片段依赖于统一值来确定正确的分支-这并没有按预期发生。因此,这里是一个主要警告(例如,避免使用超级着色器-可能是分支地狱的最大来源)。


2

如果您还没有性能问题,那很好。与常数进行比较的成本仍然非常便宜。这是有关GPU分支的很好的阅读:http : //http.developer.nvidia.com/GPUGems2/gpugems2_chapter34.html

无论如何,下面的代码片段将比if语句执行得差得多(并且可读性/可维护性差得多),但是仍然可以摆脱它:

int fx = floor(x);
int y = (fx * y2) + ((1- fx) * y1);
int b = (fx * b2) + ((1 -fx) * b1);

x = 10 * y + b;

请注意,我假设x限制在range内[0, 1]。这将无法工作,如果x> = 2或x <0。

删除的内容是将x转换为0或,1然后将错误的一个乘以0,将另一个乘以1。


由于原始测试if(x<0.5)的值fx应为round(x)floor(x + 0.5)
sam hocevar

1

有多个指令可以执行条件而无需分支;

vec4 when_eq(vec4 x, vec4 y) {
  return 1.0 - abs(sign(x - y));
}

vec4 when_neq(vec4 x, vec4 y) {
  return abs(sign(x - y));
}

vec4 when_gt(vec4 x, vec4 y) {
  return max(sign(x - y), 0.0);
}

vec4 when_lt(vec4 x, vec4 y) {
  return max(sign(y - x), 0.0);
}

vec4 when_ge(vec4 x, vec4 y) {
  return 1.0 - when_lt(x, y);
}

vec4 when_le(vec4 x, vec4 y) {
  return 1.0 - when_gt(x, y);
}

加上一些逻辑运算符;

vec4 and(vec4 a, vec4 b) {
  return a * b;
}

vec4 or(vec4 a, vec4 b) {
  return min(a + b, 1.0);
}

vec4 xor(vec4 a, vec4 b) {
  return (a + b) % 2.0;
}

vec4 not(vec4 a) {
  return 1.0 - a;
}

来源:http : //theorangeduck.com/page/avoiding-shader-conditionals

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.