【发布时间】:2017-05-23 20:08:23
【问题描述】:
我正在制作一个生成模型的图形程序。当用户执行一些动作时,着色器的行为需要改变。这些操作不仅会影响数值常量,也不会影响输入数据,它们还会影响一系列计算步骤的数量、顺序和类型。
为了解决这个问题,我想到了两个解决方案:
- 在运行时生成着色器代码,然后编译它。这非常依赖于 CPU,因为编译可能需要一些时间,但它对 GPU 非常友好。
- 使用同一着色器在运行时解释的某种字节码。这消除了再次编译着色器的需要,但现在 GPU 需要处理大量的簿记工作。
我为这两种方法开发了原型,结果比我预期的要极端。
编译时间很大程度上取决于着色器的其余部分(我猜有很多函数内联),我认为我可以重构着色器以减少每个线程的工作并缩短编译时间。但是,我现在不知道这是否足够,而且我不太喜欢运行时重新编译的想法(非常依赖于平台,更难调试,更复杂)。
另一方面,字节码方法的运行速度(不考虑第一种方法的编译时间)慢了 25 倍。
我知道字节码方法会变慢,但没想到会这样,尤其是在优化之后。
解释器通过从统一缓冲区对象中读取字节码来工作。这是它的简化,我在有用(非簿记)代码所在的位置放置了一个“...”,该部分与另一种方法相同(显然,它不在带有大 if/else 的循环内选择正确的指令):
layout (std140, binding=7) uniform shader_data{
uvec4 code[256];
};
float interpreter(vec3 init){
float d[4];
vec3 positions[3];
int dDepth=0;
positions[0]=init;
for (int i=0; i<code[128].x; i+=3){
const uint instruction=code[i].x;
const uint ldi=code[i].y;
const uint sti=code[i].z;
if (instruction==MIX){
...
}else{
if (instruction<=BOX){
if (instruction<=TRANSLATION){
if(instruction==PARA){
...
}else{//TRANSLATION;
...
}
}else{
if (instruction==EZROT){
...
}else{//BOX
...
}
}
}else{
if (instruction<=ELLI){
if (instruction==CYL){
...
}else{//ELLI
...
}
}else{
if (instruction==REPETITION){
...
}else{//MIRRORING
...
}
}
}
}
}
return d[0];
}
我的问题是:你知道为什么这么慢(因为我没有在解释器中看到这么多的簿记)吗?你能猜出这个解释器的主要性能问题是什么吗?
【问题讨论】:
-
"这个解释器的主要性能问题是什么?" Branches.
-
我知道分支会导致性能损失,但在这种情况下,分支是完全一致的(非发散的):所有线程都将采用相同的分支。您仍然认为分支是主要问题吗?我尝试使用 switch/case 和另一个 if/else 结构,但这是性能更高的版本。我什至把 MIX 指令放在首位,因为 MIX 指令几乎代表了普通字节码的 50%。
-
我的阅读理解失败了,我想我需要一些下午的咖啡 :) 对统一分支的良好调用,除了我猜测着色器大小和/或着色器编译器无法优化以及它可以在“标准”着色器上:/