Joh*_*sie 5 performance gpu glsl 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
}
Run Code Online (Sandbox Code Playgroud)
它将一些值写入x本地数组中动态确定的索引。现在,将该行替换localData[i] = x;为
for( int j = 0; j < 16; ++j )
if( i == j )
localData[j] = x;
Run Code Online (Sandbox Code Playgroud)
使代码明显更快。在几个测试示例(不同的着色器)中,执行时间几乎减半,并且发生的事情比此写入要多得多。
例如:在与顺序无关的透明度着色器中,除其他外,它获取 16 个纹素,计时采用39ms直接写入和23ms循环写入。其他什么都没变!
测试硬件是GTX1080。返回的程序集glGetProgramBinary仍然太高级。它在第一种情况下包含一行,在第二种情况下包含一个循环+if 围绕相同的行。
猜测:localData存储在 8 个 vec4 寄存器中(程序集没有说明这一点)。此外,我假设寄存器不能用索引来寻址。如果两者都为真,则最终的二进制文件必须使用某种分支构造。循环变体可能会展开并导致switch更快的类似模式。但这对于所有供应商来说都是常见的吗?为什么编译器不能使用循环的任何结果for作为此类写入的默认结果?
进一步的实验表明,原因是数组使用了不同的内存类型。(展开的)循环变体使用寄存器,而随机访问变体切换到本地内存。
本地内存通常放置在全局内存中,但对每个线程都是私有的。对该本地数组的访问很可能会被缓存(L2?)。
验证这一推理的实验如下:
展开循环的手动版本(以超过 100 万像素的 16 个元素的插入排序进行测量):
基线:localData[i] = x 33ms
For 循环:for j + if i=j 16.8ms
开关switch(i) { case 0: localData[0] ...::16.92ms
If else 树(分成两半):16.92ms
If 列表(普通手册展开):16.8ms
=> 各种分支结构都会导致或多或少相同的时序。所以这并不是像最初猜测的那样是一个糟糕的分支行为。
多个、一个、无随机访问(32 元素插入排序)
2x localData[i] = x47 毫秒
1x localData[i] = x45 毫秒
0x localData[i] = x16 毫秒
=> 只要有至少一次随机访问,性能就会很差。这意味着有一个全局决策改变了行为localData——很可能是使用不同的内存。由于缓存的存在,使用多个随机访问不会使情况变得更糟。
| 归档时间: |
|
| 查看次数: |
1254 次 |
| 最近记录: |