Yip*_*Yay 38 performance shader branch
我知道这个问题可能看起来有点没有根据,但如果有人知道任何理论/有关于这个主题的实践经验,那么如果你分享它会很棒.
我正在尝试优化我的一个旧着色器,它使用了大量的纹理查找.
我有三个可能的映射平面中的每一个的漫反射,普通,高光贴图,对于一些靠近用户的面,我还必须应用贴图技术,这也带来了很多纹理查找(如parallax occlusion mapping).
分析表明纹理查找是着色器的瓶颈,我愿意将其中一些删除.对于输入参数的某些情况,我已经知道纹理查找的一部分是不必要的,显而易见的解决方案是执行类似(伪代码)的操作:
if (part_actually_needed) {
perform lookups;
perform other steps specific for THIS PART;
}
// All other parts.
Run Code Online (Sandbox Code Playgroud)
现在 - 问题来了.
我不记得确切(这就是为什么我说这个问题可能会不接地),但在某些纸张最近,我读(不幸的是,不记得名字了)类似下面的内容指出:
所提出的技术的性能取决于基于硬件的条件分支 的实施效率.
在我即将开始重构大量着色器并实现我所讨论的if基于优化的优化之前,我记得这种说法.
所以 - 在我开始这样做之前 - 有人知道着色器中分支的效率吗?为什么分支会在着色器中造成严重的性能损失?
甚至有可能我只能通过if基于分支的方式恶化实际性能?
你可能会说 - 试试看.是的,如果这里没有人帮助我,那就是我要做的事:)
但是,在这种if情况下,新GPU可能会有效,对于一些较老的GPU来说可能是一场噩梦.除非你有很多不同的GPU(这不是我的情况),否则很难预测这类问题
所以,如果有人知道这些或具有这些着色器的基准测试经验,我将非常感谢您的帮助.
实际工作的剩余脑细胞几乎没有告诉我,GPU上的分支可能远不如分支CPU(通常具有非常有效的分支预测方法和消除缓存未命中)那么有效,仅仅因为它是GPU(或者说可能很难/不可能在GPU上实现).
不幸的是,我不确定这个陈述是否与实际情况有任何共同之处......
cas*_*nca 32
如果条件是统一的(即整个传递的常量),那么分支基本上是免费的,因为框架将基本上编译着色器的两个版本(分支采用而不是),并根据您的输入为整个传递选择其中一个变量.在这种情况下,肯定去的if声明,因为它会令你的着色器更快.
如果条件因顶点/像素而异,那么它确实会降低性能,而较旧的着色器模型甚至不支持动态分支.
小智 32
不幸的是,我认为这里的真正答案是在目标硬件上使用特定情况的性能分析器进行实际测试.特别是考虑到你处于项目优化阶段; 这是考虑硬件频繁更改和特定着色器性质这一事实的唯一方法.
在CPU上,如果你得到一个错误预测的分支,你将导致管道刷新,并且由于CPU管道太深,你将有效地丢失大约20个或更多周期的东西.在GPU上的东西有点不同; 管道可能会更浅,但没有分支预测,所有着色器代码都将在快速内存中 - 但这并不是真正的区别.
很难知道正在发生的所有事情的确切细节,因为nVidia和ATI相对守口如瓶,但关键是GPU是为大规模并行执行而制作的.有许多异步着色器核心,但每个核心再次设计为运行多个线程.我的理解是每个核心都希望在任何给定周期内对所有线程运行相同的指令(nVidia将这个线程集合称为"warp").
在这种情况下,线程可能表示顶点,几何元素或像素/片段,而warp是其中大约32个的集合.对于像素,它们可能是屏幕上彼此接近的像素.问题是,如果在一个warp中,不同的线程在条件跳转时做出不同的决定,则warp已经发散并且不再为每个线程运行相同的指令.硬件可以处理这个问题,但它(至少对我来说)它是如何做到的并不完全清楚.对于每一代牌,它的处理方式也可能略有不同.最新,最通用的CUDA /计算着色器友好型nVidias可能具有最佳实现; 较旧的卡可能有较差的实现.最糟糕的情况是你可能会发现许多线程执行if/else语句的两个方面.
着色器的一个很好的技巧是学习如何利用这种大规模并行范式.有时这意味着使用额外的通道,临时的屏幕外缓冲区和模板缓冲区来将逻辑推出着色器并进入CPU.有时优化可能会燃烧更多周期,但实际上可能会减少一些隐藏的开销.
另请注意,您可以将DirectX着色器中的if语句明确标记为[branch]或[flatten].展平样式为您提供正确的结果,但始终在说明中执行所有操作.如果你没有明确地选择一个,编译器可以为你选择一个 - 并且可以选择[flatten],这对你的例子没有好处.
要记住的一件事是,如果跳过第一个纹理查找,这将混淆硬件的纹理坐标派生数学.您将遇到编译器错误,最好不要这样做,否则您可能会错过一些更好的纹理支持.
小智 26
在许多情况下,可以通过条件计算和混合两个分支作为插值器.这种方法比分支更快.也可以在CPU上使用.例如:
...
vec3 c = vec3(1.0, 0.0, 0.0);
if (a == b)
c = vec3(0.0, 1.0, 0.0);
可以替换为:
vec3 c = mix(vec3(1.0, 0.0, 0.0), vec3(0.0, 1.0, 0.0), (a == b));
...
Sha*_*eal 10
这是一个点燃火灾的真实世界性能基准:
在片段着色器中......
运行速度为20fps:
lowp vec4 a = vec4(0.0, 0.0, 0.0, 0.0);
if (a.r == 0.0)
gl_FragColor = texture2D ( texture1, TextureCoordOut );
Run Code Online (Sandbox Code Playgroud)
这运行速度为60fps:
gl_FragColor = texture2D ( texture1, TextureCoordOut );
Run Code Online (Sandbox Code Playgroud)
我不知道基于if的优化,但是如何创建你认为你需要的纹理查找的所有排列,每个都有自己的着色器,并且只使用正确的着色器来处理正确的情况(取决于在哪个纹理上查找特定模型或模型的一部分需要).我认为我们在Bully for Xbox 360上做过类似的事情.