shader中用for,if等条件语句为什么会使得帧率降低很多
答案:1 悬赏:50 手机版
解决时间 2021-11-12 22:33
- 提问者网友:你挡着我发光了
- 2021-11-12 10:23
shader中用for,if等条件语句为什么会使得帧率降低很多
最佳答案
- 五星知识达人网友:鱼芗
- 2021-11-12 11:05
1. For和If不一定意味着动态分支
在GPU上的分支语句(for,if-else,while),可以分为三类。
Branch的Condition仅依赖编译期常数
此时编译器可以直接摊平分支,或者展开(unloop)。对于For来说,会有个权衡,如果For的次数特别多,或者body内的代码特别长,可能就不展开了,因为会指令装载也是有限或者有耗费的
额外成本可以忽略不计
Branch的Condition仅依赖编译期常数和Uniform变量
一个运行期固定的跳转语句,可预测
同一个Warp内所有micro thread均执行相同分支
额外成本很低
Branch的Condition是动态的表达式
这才是真正的“动态分支”
会存在一个Warp的Micro Thread之间各自需要走不同分支的问题
2. 跳转本身的成本非常低
随着IP/EP(Instruction Pointer/Execution Pointer)的引入,现代GPU在执行指令上的行为,和CPU没什么两样。跳转仅仅是重新设置一个寄存器。
3.Micro Thread走不同分支时的处理
GPU本身的执行速度快,是因为它一条指令可以处理多个Micro Thread的数据(SIMD)。但是这需要多个Micro Thread同一时刻的指令是相同的。
如果不同,现代GPU通常的处理方法是,按照每个Micro Thread的不同需求多次执行分支。
x = tex.Load();
if(x == 5)
{
// Thread 1 & 2 使用这个路径
out.Color = float4(1, 1, 1, 1);
}
else
{
// Thread 3 & 4 使用这个路径
out.Color = float4(0, 0, 0, 0);
}
比如在上例中,两个分支的语句Shader Unit都会执行,只是不同的是如果在执行if分支,那么计算结果将不会写入到thread 3 和 4的存储中(无副作用)。
这样做就相当于运算量增加了不少,这是动态分支的主要成本。
但是如果所有的线程,都走的是同一分支,那么另外一个分支就不用走了。这个时候Shader Unit也不会去傻逼一样的执行另外一个根本不需要执行的分支。此时性能的损失也不多。并且,在实际的Shader中,除非特殊情况,大部分Warp内的线程,即便在动态分支的情况下,也多半走的是同一分支。
4. 动态分支和代码优化难度有相关性
这一点经常被忽视,就是有动态分支的代码,因为没准你要读写点什么,前后还可能有依赖,往往也难以被优化。比如说你非要闹这样的语句出来:
if(x == 1)
{
color = tex1.Load(coord);
}
else if(x == 2)
{
color = tex2.Load(coord);
}
...
你说编译器怎么给你优化。
说句题外话,为啥要有TextureArray呢?也是为了这个场合。TextureArray除了纹理不一样,无论格式、大小、坐标、LoD、偏移,都可以是相同的。这样甚至可以预见不同Texture Surface上取数据的内存延迟也是非常接近的。这样有很多的操作都可以合并成SIMD,就比多个Texture分别来取快得多了。这就是一个通过增加了约束(纹理格式、大小、寻址坐标)把SISD优化成SIMD的例子。
在GPU上的分支语句(for,if-else,while),可以分为三类。
Branch的Condition仅依赖编译期常数
此时编译器可以直接摊平分支,或者展开(unloop)。对于For来说,会有个权衡,如果For的次数特别多,或者body内的代码特别长,可能就不展开了,因为会指令装载也是有限或者有耗费的
额外成本可以忽略不计
Branch的Condition仅依赖编译期常数和Uniform变量
一个运行期固定的跳转语句,可预测
同一个Warp内所有micro thread均执行相同分支
额外成本很低
Branch的Condition是动态的表达式
这才是真正的“动态分支”
会存在一个Warp的Micro Thread之间各自需要走不同分支的问题
2. 跳转本身的成本非常低
随着IP/EP(Instruction Pointer/Execution Pointer)的引入,现代GPU在执行指令上的行为,和CPU没什么两样。跳转仅仅是重新设置一个寄存器。
3.Micro Thread走不同分支时的处理
GPU本身的执行速度快,是因为它一条指令可以处理多个Micro Thread的数据(SIMD)。但是这需要多个Micro Thread同一时刻的指令是相同的。
如果不同,现代GPU通常的处理方法是,按照每个Micro Thread的不同需求多次执行分支。
x = tex.Load();
if(x == 5)
{
// Thread 1 & 2 使用这个路径
out.Color = float4(1, 1, 1, 1);
}
else
{
// Thread 3 & 4 使用这个路径
out.Color = float4(0, 0, 0, 0);
}
比如在上例中,两个分支的语句Shader Unit都会执行,只是不同的是如果在执行if分支,那么计算结果将不会写入到thread 3 和 4的存储中(无副作用)。
这样做就相当于运算量增加了不少,这是动态分支的主要成本。
但是如果所有的线程,都走的是同一分支,那么另外一个分支就不用走了。这个时候Shader Unit也不会去傻逼一样的执行另外一个根本不需要执行的分支。此时性能的损失也不多。并且,在实际的Shader中,除非特殊情况,大部分Warp内的线程,即便在动态分支的情况下,也多半走的是同一分支。
4. 动态分支和代码优化难度有相关性
这一点经常被忽视,就是有动态分支的代码,因为没准你要读写点什么,前后还可能有依赖,往往也难以被优化。比如说你非要闹这样的语句出来:
if(x == 1)
{
color = tex1.Load(coord);
}
else if(x == 2)
{
color = tex2.Load(coord);
}
...
你说编译器怎么给你优化。
说句题外话,为啥要有TextureArray呢?也是为了这个场合。TextureArray除了纹理不一样,无论格式、大小、坐标、LoD、偏移,都可以是相同的。这样甚至可以预见不同Texture Surface上取数据的内存延迟也是非常接近的。这样有很多的操作都可以合并成SIMD,就比多个Texture分别来取快得多了。这就是一个通过增加了约束(纹理格式、大小、寻址坐标)把SISD优化成SIMD的例子。
我要举报
如以上回答内容为低俗、色情、不良、暴力、侵权、涉及违法等信息,可以点下面链接进行举报!
点此我要举报以上问答信息
大家都在看
推荐资讯