代码之家  ›  专栏  ›  技术社区  ›  Yippie-Ki-Yay

着色器中分支的效率

  •  36
  • Yippie-Ki-Yay  · 技术社区  · 14 年前

    我知道这个问题似乎有些不确定,但是 如果有人知道什么理论知识/有实践经验 关于这个话题,如果你能分享一下,那就太好了。

    我正在尝试优化我的一个旧着色器,它使用了很多纹理查找。

    我有漫反射,正常,镜面反射的地图 对于三个可能的映射平面中的每一个,以及一些靠近用户的面,我还必须应用映射技术,这也会带来很多纹理查找(比如 parallax occlusion mapping ).

    分析表明,纹理查找是着色器的瓶颈,我愿意删除其中一些。为了 输入参数的一些情况 我已经知道部分纹理查找是不必要的 明显的 解决办法是做些 (伪码) :

    if (part_actually_needed) {
       perform lookups;
       perform other steps specific for THIS PART;
    }
    
    // All other parts.
    

    现在-问题来了。

    我记不清了(这就是为什么我说问题可能是 不接地 ),但在我最近读到的一篇论文中 (不幸的是,记不起名字) 有人说了类似的话:

    这个 性能 所呈现的 技术取决于效率 这个 基于硬件的条件 分支 已实现。

    在我准备开始重构大量的着色器并实现它之前,我就记住了这种说法 if -基于优化 我在说。

    所以- 在我开始之前 -有人知道在阴影中分支的效率吗?为什么分支会给着色器带来严重的性能损失?

    如果 -基于分支?


    你可能会说- 试试看。 是的,如果这里没有人帮我,我会这么做的:)

    但是,在 如果 对于新的GPU来说,这个案例可能是有效的,对于稍老一点的GPU来说,这可能是一个噩梦。 而且这种问题很难预测,除非你有很多不同的GPU(这不是我的情况)

    所以,如果有人知道这方面的情况,或有这些类型的阴影基准的经验,我将非常感谢你的帮助。


    剩下的几个实际工作的脑细胞一直告诉我,在GPU上进行分支可能远不如在CPU上进行分支有效(CPU通常有非常有效的分支预测和消除缓存未命中的方法),因为它是一个GPU(或者在GPU上很难/不可能实现)。

    不幸的是,我不确定这句话是否与实际情况有什么共同之处。。。

    5 回复  |  直到 10 年前
        1
  •  32
  •   David Jewsbury    14 年前

    不幸的是,我认为这里真正的答案是在你的目标硬件上用你的特定情况的性能分析器进行实际测试。特别是考虑到听起来你正处于项目优化阶段;这是唯一考虑到硬件频繁变化和特定材质球性质的方法。

    在CPU上,如果出现预测失误的分支,则会导致管道刷新,而且由于CPU管道太深,实际上会丢失20个或更多周期的内容。在GPU上,情况有点不同;管道可能要浅得多,但没有分支预测,所有的着色器代码都将在快速内存中——但这并不是真正的区别。

    很难知道每件事情的确切细节,因为nVidia和ATI都比较守口如瓶,但关键是gpu是为大规模并行执行而设计的。有许多异步着色器内核,但每个内核都被设计为运行多个线程。我的理解是,每个核心都希望在任何给定周期的所有线程上运行相同的指令(nVidia将此线程集合称为“warp”)。

    在这种情况下,一个线程可能代表一个顶点、一个几何元素或一个像素/片段,而warp则是其中大约32个元素的集合。对于像素,它们很可能是屏幕上彼此靠近的像素。问题是,如果在一个warp中,不同的线程在条件跳转时做出不同的决定,那么warp已经发散,不再为每个线程运行相同的指令。硬件可以处理这个问题,但(至少对我来说)它是如何做到的还不完全清楚。对于每一代连续的卡片,处理方式也可能略有不同。最新的、最通用的CUDA/compute着色友好nvidia可能有最好的实现;较旧的卡可能有较差的实现。更糟糕的情况是,您可能会发现许多线程同时执行if/else语句的两边。

    使用着色器的一个重要技巧是学习如何利用这种大规模并行范式。有时这意味着使用额外的过程、临时的屏幕外缓冲区和模板缓冲区将逻辑向上推离着色器并推到CPU上。有时,优化可能会消耗更多的周期,但实际上可能会减少一些隐藏的开销。

    还要注意,可以显式地将DirectX着色器中的if语句标记为[branch]或[flatten]。扁平样式为您提供正确的结果,但始终执行指令中的所有内容。如果您没有显式地选择一个,编译器可以为您选择一个——并且可以选择[flatten],这对您的示例不好。

    要记住的一点是,如果跳过第一个纹理查找,这将混淆硬件的纹理坐标导数数学。你会遇到编译器错误,最好不要这样做,否则你可能会错过一些更好的纹理支持。

        2
  •  32
  •   casablanca    14 年前

    如果条件是一致的(即整个过程的常量),则分支基本上是自由的,因为框架将基本上编译着色器的两个版本(执行分支和不执行分支),并根据输入变量为整个过程选择其中一个。在这种情况下,一定要 if 陈述 使材质球更快。

    如果每个顶点/像素的条件都不同,那么它确实会降低性能,而旧的着色器模型甚至不支持动态分支。

        3
  •  26
  •   Plushechnik    10 年前

    在许多情况下,这两个分支都可以通过条件作为内插器进行计算和混合。 这种方法比branch快得多。也可以在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));

    ...

        4
  •  10
  •   Shaun Neal    12 年前

    以下是kindle Fire的真实性能基准:

    在片段着色器中。。。

    它以每秒20帧的速度运行:

    lowp vec4 a = vec4(0.0, 0.0, 0.0, 0.0);
    if (a.r == 0.0)
        gl_FragColor = texture2D ( texture1, TextureCoordOut );   
    

    它以每秒60帧的速度运行:

    gl_FragColor = texture2D ( texture1, TextureCoordOut );   
    
        5
  •  8
  •   Jim Buck    14 年前

    我不知道基于if的优化,但是如何创建您认为需要的所有纹理查找排列,每个都有自己的着色器,并在正确的情况下使用正确的着色器(取决于需要哪个纹理查找特定模型或模型的一部分)。我想我们在Xbox360的霸王游戏上做了类似的事情。