GLSL:为什么本地数组中的随机写入比循环写入慢得多?

时间:2018-04-13 13:40:48

标签: performance glsl gpu compiler-optimization

让我们看一下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;

使代码显着更快。在几个经过测试的示例(不同着色器)中,执行时间几乎减半,并且还有比这次写入更多的事情。

例如:在一个与顺序无关的透明度着色器中,除其他外,它使用直接写入39ms和带有循环写入的23ms来获取16个纹素。没有其他改变!

测试硬件是GTX1080。 glGetProgramBinary返回的程序集仍然过高。它在第一种情况下包含一行,如果在第二种情况下包含相同的行,则包含循环+。

  • 为什么会出现这种性能问题?
  • 这适用于所有供应商吗?

猜猜:localData存储在8个vec4寄存器中(程序集没有说明任何内容)。我还假设,寄存器不能用索引来寻址。如果两者都为真,那么最终的二进制必须使用一些分支构造。循环变体可能会展开并导致类似switch的模式更快。但这对所有供应商来说都很常见吗?为什么编译器不能使用来自for循环的结果作为此类写入的默认值?

1 个答案:

答案 0 :(得分:1)

进一步的实验表明,原因是对数组使用不同的内存类型。 (展开的)循环变体使用寄存器,而随机访问变体切换到本地存储器

本地内存通常放在全局内存中,但对每个线程都是私有的。可能会缓存对此本地数组的访问(L2?)。

验证这种推理的实验如下:

  1. 展开循环的手动版本(以插入排序方式测量,包含超过1M像素的16个元素):

    基线:localData[i] = x 33ms
    for循环:for j + if i=j 16.8ms
    开关:switch(i) { case 0: localData[0] ...:16.92ms
    如果是树(分成两半):16.92ms
    如果列表(普通手动展开):16.8ms

    =&GT;各种分支结构导致或多或少相同的时序。因此,最初的猜测并不是一个糟糕的分支行为。

  2. 多对一对比无随机访问(32元素插入排序)

    2x localData[i] = x 47ms
    1x localData[i] = x 45ms
    0x localData[i] = x 16ms

    =&GT;只要至少有一个随机访问,性能就会很糟糕。这意味着有一个全局决定改变localData的行为 - 很可能是使用不同的内存。由于缓存,使用多个随机访问不会使事情变得更糟。