首页 > 解决方案 > 着色器的 for 循环中的条件中断性能

问题描述

开始学习着色器后,我读到的第一件事是出于性能原因必须尽可能避免(动态)条件分支:显然两个分支都将运行,然后根据条件选择结果。

然而,在查看示例着色器时,我在 Shadertoy 上发现了这个自动立体图着色器。在第 30 行,在 main 函数中,我们可以看到:

for(int count = 0; count < 100; count++) {
    if(uv.x < pWid)
        break;

    float d = getDepth(uv);
    //d = 1.;

    uv.x -= pWid - (d * maxStep);
}

在这里,我们在for循环中有一个条件中断。天真地,基于上面的“无条件分支”,人们会认为它的性能很糟糕,因为每个循环出现都有一个分支(这里是 100)。然而,这种情况并非如此。事实上,将最大循环数从 100 增加到任何巨大的数字对性能没有明显影响。

我们可以取消分支,例如使用以下代码:

for(int count = 0; count < 100; count++) {
    float d = getDepth(uv);
    //d = 1.;

    uv.x -= (pWid - (d * maxStep)) * step(0.0, uv.x-pWid);
}

但随后性能受到更大循环的影响:在 1000 或 10000 时,它会减速到爬行。

(类似地,用breaka替换continue会随着更大的循环而减慢,尽管不会那么多。)

因此,如果它没有运行所有可能的条件分支,那么这里到底发生了什么?在哪些情况下可以使用动态分支而不会影响性能?

标签: performancewebglshader

解决方案


使用现代 GPU,需要着色的(在这种情况下)像素的总工作量被分成瓦片/组,然后瓦片由“warp”/“wavefront”¹处理,这是一组同步运行的线程这意味着处理 tile 的 warp 中的所有线程都运行相同的指令,但数据不同(SIMD)。

想象有一个处理 2x2 像素的扭曲,其中三个像素需要 10 次迭代,但第四个需要 100 次迭代,因此所有线程都运行 100 次迭代,前三个像素的多余 90 次迭代的结果将被“屏蔽”/丢弃所以它不会影响它们的输出,但是当所有线程都完成处理时,整个扭曲可能只会移动到下一个像素。但是,当所有线程在 10 次迭代后退出时,warp 可能会退出并更早移动,因此您将获得性能提升。

您可以在此处找到对上述内容的(稍微)更长的解释。

在这里一窥现代 GPU 上调度/平铺的内部工作原理。

¹ “warp”是 NVIDIA,“wavefront”是 AMD 术语


推荐阅读