GPU的shader分支跳转性能总结

引言:

如下的(一)与(二)分别属于uniform branch与宏定义,(一)至始至终是一个固定的值,分支只执行一条而不是既有执行condition ture 也有执行condition false 的情况,(二)使用宏在编译期完成分支跳转,它们两个的性能消耗是怎么样的呢?在很多的博客或者教科书方式的说GPU的跳转分支会中断GPU的并行,会增加耗时等,哪现代的GPU对于分支跳转的性能是怎么样的!作者本人在此做一个总结;
一、

#version 330 core
layout(location = 0) in vec3 aPos;
uniform bool useColorA;
out vec4 vertexColor;
void main()
{
    gl_Position = vec4(aPos, 1.0);
    if (useColorA)
    {
        vertexColor = vec4(1.0, 0.0, 0.0, 1.0); // Red
    }
    else
    {
        vertexColor = vec4(0.0, 0.0, 1.0, 1.0); // Blue
    }
}

二、

#version 330 core
layout(location = 0) in vec3 aPos;
// 宏定义
#define USE_COLOR_A
out vec4 vertexColor;
void main()
{
    gl_Position = vec4(aPos, 1.0);
#ifdef USE_COLOR_A
    vertexColor = vec4(1.0, 0.0, 0.0, 1.0); // Red
#else
    vertexColor = vec4(0.0, 0.0, 1.0, 1.0); // Blue
#endif
}

if-else 分支性能消耗情况:

  1. 现代的 GPU 不管是 PC 端还是移动端对于条件分支 if -else 都有movc/condition move硬件指令优化机制。该指令可以减少由于分支指令引起的pipline暂停和分支预测错误。在这里插入图片描述

如果一个 在Warp(内shader代码指令相同)内所有的线程同时采取同一路径的分支,那么就没有性能损失!或者说分支是基于编译器能够预测优化的uniform变量再或者分支里的操作很简单开销也非常小,以至于可以忽略该开销!(iPhone 在 A8 后都支持movc优化)
2、if 分支只有出现线程分歧 (thread divergency)才会影响GPU并行的性能!在执行同一个warp的线程中有一些满足条件而另一些不满足,则将分别执行代码块A和代码块B。由于SIMD或SIMT的限制,不能同时执行两个代码块,GPU序列化处理这些分支。

  • 所有线程同时执行代码块A,未满足条件的线程暂停。
  • 所有线程完成后,执行代码块B,满足条件的线程暂停。
    案例(三)触发thread divergency
in vec2 TexCoord;
out vec4 FragColor;
// 假设有两个不同的纹理
uniform sampler2D texture1;
uniform sampler2D texture2;
void main()
{
    vec4 color;
    if (TexCoord.x < 0.5)
    {
        color = texture(texture1, TexCoord);
    }
    else
    {
        color = texture(texture2, TexCoord);
    }
    FragColor = color;
}

案例(四)也触发thread divergency

in vec3 FragPos;
in vec3 Normal;
out vec4 FragColor;
uniform vec3 lightPos;
uniform vec3 viewPos;
void main()
{
    vec3 norm = normalize(Normal);
    vec3 lightDir = normalize(lightPos - FragPos);
    vec3 result;
    if (dot(norm, lightDir) > 0.5)      // 基于法线方向选择不同的光照模型
    {
        float diff = max(dot(norm, lightDir), 0.0);  漫反射光照模型
        result = diff * vec3(1.0, 0.5, 0.31); // 硬编码的漫反射颜色
    }
    else
    {
        vec3 viewDir = normalize(viewPos - FragPos);            // Phong 光照模型
        vec3 reflectDir = reflect(-lightDir, norm);
        float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32.0);
        result = (0.5 * vec3(1.0, 0.5, 0.31)) + (spec * vec3(0.5, 0.5, 0.5)); // 漫反射 + 镜面反射
    }
    FragColor = vec4(result, 1.0);
}

在这里插入图片描述
如上图 SM中有8个ALU(Core),由于SIMD/SIMT特性,每个ALU的数据不一样,导致if-else语句在某些ALU中执行的是true分支(黄色),有些ALU执行的是false分支(灰蓝色),这样导致很多ALU的执行周期被浪费掉 (masked out)并且拉长了整个执行周期。最坏的情况,同一个SM中只有1/8(8是同一个SM的线程数,不同架构的GPU有所不同)的利用率。实际上,这意味着即使每个线程只执行其中一个分支,整个Warp需要等待所有分支完成,导致线程分歧。
3、 现代的gpu对于uniform branch都有movc优化分支,如上(1)中提到的只会跑一个路径但是vgpr(Vector General Purpose Registers)你得付出2倍。vgpr 使用率直接影响了GPU能够并发执行的着色器线程的数量,movc 过于复杂要求更多的vgpr来存储临时结果、中间状态和最终输出。导致更长的指令序列,增加执行时间。由于 寄存器占用过多,减少可以同时执行的线程数量, 产生 Register spilling(将寄存器写入主存),综上所述要是movc 后太复杂即便只走一次也影响vgpr从而影响性能!
4、对于uniform branch 推荐使用特化常量(specialization constants),顾名思义特化常量是shader编译器编译阶段已知。展开计算或移除不必要的代码路径,常量的使用有助于减少运行时分支和寄存器的使用,从而提升性能。当然spec const 是对于 vk 在 opengles 中并没有,所以在 GLSL 中只能使用宏来定义实现类似于vk 的spec const 条件语句(不会像(3)上说的有vgpr问题) 。

各大平台实测:

如上提到的根据作者本人的实际测试uniform branch 对于if-else与宏之间的性能差异大致如下:
在这里插入图片描述

综上所述:实测可见条件分支 if -else在uniform branch情况下并不会比宏消耗的更多,所以一开始举例(一)与(二)准确来说是一样的,因为现代的GPU shader有movc/condition move硬件指令优化机制。

VGPR:

居然上面提到了vgpr这里单独讨论一下补一下GPU的寄存器的问题!vgpr(Vector General Purpose Registers)是向量通用寄存器,是 GPU 架构中的一个core重要组成部分,用于存储矢量数据和临时计算结果。矢量寄存器的数量和管理对 GPU 性能有极大影响,尤其在处理并行计算任务时。vgpr 主要用于保存线程执行过程中需要的各种数据,包括顶点属性、纹理坐标和中间计算结果等。
它的数量与其架构和设计紧密相关,不同的芯片厂商和型号会有显著差异。在移动端,高通(Qualcomm)、ARM(Mali)、以及苹果(PowerVR)都是主要厂商,而在 PC 端,AMD 和 NVIDIA 是主要的 GPU 制造商都不同!对于Mali-G77,每个计算核也会拥有几十到几百个 VGPR,这因核数和具体型号不同而异。对于Apple GPU与Qualcomm Adreno GPU 没有公开具体数量我也没找到!预估是上百个到一千之间(移动端的功耗与集成度决定的),但是PC比如之前英伟达的费米架构超过了2W的vgpr!
底下有两篇博客关于VGPR的优化(所以在移动端也不要小瞧if-else增加的2倍vgpr,当然在PC端可以不关系的) Optimizing GPU occupancy and resource usage with large thread groups,与vgpr寄存器的压力 How to reduce register pressure

step、三元运算符的性能消耗情况
一、对于三元运算符"?"它就是一个语法糖,在不同的 shader language等价不同! 在hlsl和cg的三元运算符是和lerp step是等效的 gslsl是if-else 的dynamic branching,详细参考:Shader optimization: Is a ternary operator equivalent to branching?
二、step与 if-else 性能对比,首先不是所有的 step 都能替换 if-else 的,所以只能在能替换的情况下讨论,理论预计step 比 if-else 的非thread divergency(movc)消耗性能要看具体情况,如下unity的例子是优化的 Unity Shader: 优化GPU代码–用step()代替if else等条件语句,如下相对于案例(三)就是一种负优化!因为增加了纹理的采样!2D纹理的采样消耗是增加了一次双线性插值与LDU的花销!!!

in vec2 TexCoord;
out vec4 FragColor;
uniform sampler2D texture1;
uniform sampler2D texture2;
void main()
{
    float condition = step(0.5, TexCoord.x);
    vec4 color1 = texture(texture1, TexCoord);
    vec4 color2 = texture(texture2, TexCoord);
   
    // 使用混合来避免 if-else
    vec4 color = mix(color1, color2, condition);
    FragColor = color;
}

建议:

  1. 尽量不要使用分支,如必须使用的话,优先选择常量的判定条件,其次选择 uniform 变量作为判定条件。
  2. 最糟糕的情况是使用 shader 内部计算的值作为判定条件,尽可能避免。
  3. 最终确定要使用分支,请确保两条分支不存在大量重复代码。大量重复代码会导致 shader 占用VGPR明显增多,减少 active warp 数量,最终导致性能下降。
  4. 尽管 step()`和 mix() 在某些情况下有助于优化性能,但它们可能会降低代码的可读性和维护性。这是一个权衡,需要在性能优化和代码清晰度之间找到平衡点。

参考资料:

vulkan uniform、推送常量、特化常量概念和用法
添加链接描述图形引擎实战:游戏GPU性能优化
深入GPU硬件架构及运行机制
GPU硬件架构概述
移动端GPU

  • 20
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
自动控制节水灌溉技术的高低代表着农业现代化的发展状况,灌溉系统自动化水平较低是制约我国高效农业发展的主要原因。本文就此问题研究了单片机控制的滴灌节水灌溉系统,该系统可对不同土壤的湿度进行监控,并按照作物对土壤湿度的要求进行适时、适量灌水,其核心是单片机和PC机构成的控制部分,主要对土壤湿度与灌水量之间的关系、灌溉控制技术及设备系统的硬件、软件编程各个部分进行了深入的研究。 单片机控制部分采用上下位机的形式。下位机硬件部分选用AT89C51单片机为核心,主要由土壤湿度传感器,信号处理电路,显示电路,输出控制电路,故障报警电路等组成,软件选用汇编语言编程。上位机选用586型以上PC机,通过MAX232芯片实现同下位机的电平转换功能,上下位机之间通过串行通信方式进行数据的双向传输,软件选用VB高级编程语言以建立友好的人机界面。系统主要具有以下功能:可在PC机提供的人机对话界面上设置作物要求的土壤湿度相关参数;单片机可将土壤湿度传感器检测到的土壤湿度模拟量转换成数字量,显示于LED显示器上,同时单片机可采用串行通信方式将此湿度值传输到PC机上;PC机通过其内设程序计算出所需的灌水量和灌水时间,且显示于界面上,并将有关的灌水信息反馈给单片机,若需灌水,则单片机系统启动鸣音报警,发出灌水信号,并经放大驱动设备,开启电磁阀进行倒计时定时灌水,若不需灌水,即PC机上显示的灌水量和灌水时间均为0,系统不进行灌水。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值