OpenGL shader性能优化策略(一):减少分支语句

一、优化策略:减少使用分支语句

在编写OpenGL shader时,一定要注意减少使用if或for语句,因为这些语句引入分支、会大大降低shader的性能,得不偿失。之所以if语句会对性能有这么大的影响,要从OpenGL的运行机制说起。

二、GPU计算原理:wavefront

以OpenGL通常处理的图像来说,OpenGL的shader在运算的时候,会产生成千上万个线程来对不同的点位区域进行计算,每个线程都使用同一份shader代码、但是处理的数据不同。为了大幅度提高计算速度,OpenGL利用了GPU,而GPU的基本调度单位叫做wavefront(不同平台理念相同、叫法不同,NVIDIA平台叫warp,AMD平台叫wavefront等,本文统称为wavefront)。wavefront是一组线程的组合,既然称之为调度的基本单位,自然是GPU会同时处理属于同一个wavefront的所有线程,因为他们的计算指令(shader)从第一行到最后一行是完全相同的,只是数据不同而已。GPU正是这样通过single instruction multiple thread(SIMT)的方式来进行提速的。这有点类似于CPU中的SIMD加速,只不过CPU中一次SIMD操作只针对一组数据、需要人为编码控制,而GPU的SIMT是从始至终的用相同指令计算所有的线程数据。这样并行度极高,从而大幅提升了性能。

wavefront很形象,中文叫做波阵面,如下图所示。可以看出来是多个线程持续不断的同步计算,每次计算指令相同、uniform部分参数相同、定值参数相同,只有传入纹理、varying参数以及一些本地计算数据等不同而已。

但是一旦引入if/for产生分支,wavefront结构就被完全破坏掉了,会产生diverged wavefront。例如原本4个线程组成一个wavefront一直同步计算,突然遇到if语句,3个线程if判断为true,进入A分支;另一个线程if判断为false,进入B分支,此时这4个线程接下来的指令不再相同,原来的这个线程组wavefront就无法同步计算、被迫分开,即为diverged wavefront。这时候,GPU只能分开执行这两个新产生的wavefront。由于GPU计算资源也是一定的,新产生的两个wavefront可能需要排队等待来顺序执行(原来是并行执行),尤其是wavefront大批量diverged的时候;然后新分割出来的wavefront如果要移动到其他GPU计算单元上还需要进行数据复制转移,也是很耗时的行为。这些都严重破坏了并行度,从而导致性能下降。因此,建议最好少使用产生分支的if语句;for语句有时候也会产生分支,也需要注意。

三、分支语句优化思路

但是很多场景下如果一定需要if语句怎么办呢?通常有以下几种思路:

1、trick方式跳过if:

一些简单的场景可以用OpenGL的step方法把if语句替换。例如原本逻辑为:

可以改为:

其他场景也可以用step函数解决,例如原始逻辑为:

可以改为:

因为step方法属于shader内置函数,要比直接使用if耗时减少不少。

step函数是OpenGL内置的,它会比较传入的两个参数的大小,进而返回0或1。

2、部分分支可被编译优化:

编译器有时可以对分支进行一定的优化。If判断条件一般包含三种数据:

(1)静态分支:If判断语句仅仅包含常数;

(2)uniform数据分支:If判断语句仅仅包含常数或uniform参数;

(3)动态分支:其他情况,If判断语句中有动态变化的数据。

按道理来说,静态数据和uniform数据不会变化,编译器应该可以判断并进行编译优化,但是对于Android开发来说,硬件千差万别,目前据我了解,对于OpenGL ES 2.0,基本上大都只能优化静态分支;对于OpenGL ES 3.0,通常可以优化uniform数据分支,部分机型可能可以优化动态分支。

所以,写分支的时候注意分支的类型,并且如果升级到OpenGL ES 3.0,就基本可以使用uniform数据分支而没有明显的性能损失了。

同理,如果for循环的此时是一个整数、即常量,那么也不会产生分支;只有当for循环的次数也是随着点位的不同动态变化的时候才会产生分支。

3、相同区块情况可以使用分支:

一般来说,相邻的点位区域的线程会组合在同一个wavefront中,如果一个分支与位置相关,例如图像上半部分都是黑色,下半部分是彩色;而If判断条件是颜色是否为黑色,那么大部分情况下同一个wavefront的线程都会在if判断后走同一个分支,这样wavefront就不会diverge。或者判断条件是和位置有关的,那么大概率也不会diverge。只要不产生diverge就不会对性能有很大影响。

4、全量代码,但保证某些分支不起作用:

此外,经过测试,假设If可以产生两个分支,将两个分支的指令全部执行完可能还会比使用If判断还要快。下面举例说明。假设当前需要根据a的值来选择计算result的方法,通常代码如下:

 那么为了减少diverge,以上代码可以改写为:

如果不是a==0的情况也可以通过step方式来转换。很多情况下,全量执行所有分支的代码比使用If判断还要快,这个可以通过实际测试比较来进行选择。 

四、总结

本文主要提出的优化思路就是在OpenGL的shader中尽量少使用if/for等分支语句,因为这会破坏GPU的wavefront结构,从而造成性能损失。如果非用不可也可以参考文中提到的4种策略。总体来说,GPU擅长的是计算而非逻辑判断,所以和逻辑有关的事情还是不在GPU上操作为好。

作者简介:alex, 天天P图AND工程师


文章后记: 天天P图是由腾讯公司开发的业内领先的图像处理,相机美拍的APP。欢迎扫码或搜索关注我们的微信公众号:“天天P图攻城狮”,那上面将陆续公开分享我们的技术实践,期待一起交流学习! 加入我们: 天天P图技术团队长期招聘: (1) AND / iOS 开发工程师 (2) 图像处理算法工程师  期待对我们感兴趣或者有推荐的技术牛人加入我们(base 上海)!联系方式:ttpic_dev@qq.com

原文发布于微信公众号 - 天天P图攻城狮(ttpic_dev)

原文发表时间:2018-12-07

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Spark学习技巧

第3篇:更新异常与规范化设计

第三篇:更新异常与规范化设计 前言 在前两篇中,主要讲了ER建模和关系建模。在具体分析如何用数据库管理软件RDBMS(Relational Database M...

4497
来自专栏CSDN技术头条

使用hadoop进行大规模数据的全局排序

1. Hellow hadoop~~! Hadoop(某人儿子的一只虚拟大象的名字)是一个复杂到极致,又简单到极致的东西。 说它复杂,是因为一个hadoop...

3475
来自专栏大数据文摘

干货 | 男朋友老是说自己R语言很6,快来用这40道题目检测他

2104
来自专栏js编程在工科课程中的简单应用

3.1 Contour绘制

首先一起学习利用百度的开源项目绘制contour,百度搜索“echarts heatmap”,找到热力图的一个案例(http://echarts.baidu.c...

1260
来自专栏恰童鞋骚年

OOAD利器之UML基础

UML:Unified Modeling Language,即统一建模语言,简单地说就是一种有特殊用处的语言。本文是我初步学习UML的学习笔记,对于我们菜鸟码农...

913
来自专栏数说工作室

换个姿势学量化!|【量化小讲堂】使用python计算各类移动平均线

作者:邢不行 原文链接: http://bbs.pinggu.org/thread-3631776-1-1.html (本文已获作者授权转载,如需转载请与原作者...

46411
来自专栏崔庆才的专栏

跟繁琐的模型说拜拜!深度学习脚手架 ModelZoo 来袭!

好多天没有更新原创文章了,国庆前的一段时间确实比较忙,整个九月在参加各种面试,另外还有公司的项目,还有自己的毕设,另外还需要准备参加一些活动和讲座,时间排的很紧...

2702
来自专栏生信宝典

R包reshape2,轻松实现长、宽数据表格转换

本文翻译自外文博客,原文链接:https://seananderson.ca/2013/10/19/reshape/

2112
来自专栏PPV课数据科学社区

【学习】使用hadoop进行大规模数据的全局排序

1. Hellow hadoop~~! Hadoop(某人儿子的一只虚拟大象的名字)是一个复杂到极致,又简单到极致的东西。 说它复杂,是因为一个hadoop...

3033
来自专栏华章科技

以卖香蕉为例,从4个方面了解SQL的数据汇总

导读:面对一个新数据集时,人们往往会关心数据中的异常值、数据的分布形式、行列之间的关系等。SQL是一种专为数据计算设计的语言,其中已经内置了许多数据汇总函数,也...

913

扫码关注云+社区

领取腾讯云代金券