GLSL:为什么局部数组的随机写入比循环写入慢得多?

5
让我们看一个简化的GLSL函数示例:

以下是示例代码:

void foo() {
    vec2 localData[16];
    // ...
    int i = ... // somehow dependent on dynamic data (not known at compile time)
    localData[i] = x; // THE IMPORTANT LINE
}

它将一些值x写入本地数组中动态确定的索引。现在,将行localData [i] = x;替换为

for( int j = 0; j < 16; ++j )
    if( i == j )
        localData[j] = x;

使用循环写入代码可以使执行时间几乎减半,测试示例(不同的着色器)中执行时间几乎减少了一半,而且比这个写入操作更复杂。

例如:在一个无序透明着色器中,除了获取16个纹理之外,还有其他事情要做,在直接写入时计时为39ms,在循环写入时计时为23ms。没有其他改变!

测试硬件是GTX1080。由glGetProgramBinary返回的汇编仍然过于高级。第一个案例中只包含一行,而第二个案例中包含一个循环+if语句围绕着相同的行。

  • 为什么会出现这种性能问题?
  • 所有供应商都是如此吗?

猜测:localData存储在8个vec4寄存器中(汇编并未说明)。进一步假设,寄存器不能用索引进行寻址。如果两者都是真的,则最终二进制必须使用某种分支结构。循环变量可能会展开,并产生类似于switch的模式,这样会更快。但这对所有供应商来说都是普遍的吗?为什么编译器不能使用for循环的任何结果作为此类写入的默认值?


请参阅 https://www.gamedev.net/forums/topic/688607-glsl-shader-slow-when-having-too-big-arrays/5342183/(关于在着色器中声明大数组时速度变慢的问题)。 - Yves M.
1个回答

3
进一步的实验表明,原因在于数组使用了不同的内存类型。展开循环变体使用寄存器,而随机访问变体则切换到本地内存。
本地内存通常位于全局内存中,但对每个线程是私有的。访问这个本地数组可能会被缓存(L2?)。
验证这种推理的实验如下:
1. 手动版本的展开循环(在一个包含1M像素的16个元素插入排序中测量):
基准线:localData[i] = x 33ms for循环:for j + if i=j 16.8ms switch:switch(i) { case 0: localData[0] ... 16.92ms if else树(分成两半):16.92ms if列表(纯手动展开):16.8ms
=>所有类型的分支结构产生更多或更少相同的时间。所以它不是最初猜测的坏分支行为。
2. 多个与一个与没有随机访问(32元素插入排序)
2x localData[i] = x 47ms 1x localData[i] = x 45ms 0x localData[i] = x 16ms
=>只要有至少一个随机访问,性能就会很差。这意味着存在改变localData行为的全局决策——很可能是使用了不同的内存。使用多个随机访问并不会使事情变得更糟,因为有缓存。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接