这章将会说明一些kernel优化的小技巧。

8.1 kernel合并或者拆分

一个复杂的应用程序可能包含很多步骤。对于OpenCL的移植性和优化,可能会问需要开发有多少个kernel。这个问题很难回答,因为这涉及到很多的因素。下面是一些准则:

  • 内存和计算之间的平衡。
  • 足够多的wave来隐藏延迟。
  • 没有寄存器溢出。

上面的要求可以通过执行以下操作实现:

  • 如果这样做能够带来更好的数据并行,将一个大的kernel拆分成多个小的kernel。
  • 如果内存的流量能够减少而且同样能保证并行性,可以将多个kernel合并成一个kernel,例如workgroup的尺寸能够足够地大。

8.2 编译选项

OpenCL支持一些编译选项,参考文献的《The OpenCLSpecification》的5.6.4节中进行了定义。编译选项可以通过APIsclCompileProgram和clBuildProgram传递。多个编译选项可以结合,如下所示。

clBuildProgram( myProgram,

numDevices,

pDevices,

“-cl-fast-relaxed-math ”,

NULL,

NULL );

通过这些选项,开发者能够针对他们自己的需求使能某些功能。比如,使用-cl-fast-relaxed-math,kernel会编译成使用快速数学函数而不是OpenCL标准函数,每一个OpenCL的说明中OpenCL标准函数都有很高的精度要求。

8.3 一致性 vs. 快速 vs. vs. 内部的数学函数

OpenCL标准在OpenCL C语言中定义了许多数学函数,默认情况下,因为OpenCL规范说明书的要求,所有的数学函数都必须满足IEEE 754 单精度的浮点精度数学要求。Adreno GPU有一个内嵌的硬件模块,EFU(elementary function unit 基本函数单元),来加速一些初级的数学函数。对于许多EFU不能直接支持的数学函数,可以通过结合EFU和ALU操作来优化,或者通过编译器使用复杂的算法来模拟进行优化。表8-1展示了OpenCL-GPU 数学函数的列表,并按照他们的相对性能来分类的。使用更好性能的函数是个较好的方法,比如使用A类中的函数

表8-1 OpenCL数学函数的性能(符合IEEE 754标准)

类别

实现

函数(可参考OpenCL标准获取更多细节)

A

仅简单使用ALU指令

ceil,copysign,fabs,fdim, floor,fmax, fmin, fract,frexp,ilogb, mad, maxmag,minmag,modf,nan,nextafter,rint,round,trunk

B

仅使用EFU,或者EFU机上简单的ALU指令

asin,asinpi,atan,atanh,atanpi,cosh,exp,exp2,rsqrt,sqrt,tanh

C

ALU,EFU,和位操作的结合

acos,acosh, acospi,asinh, atan, atan2pi,cbrt,cos,cospi,exp10,expml,fmod,hypot,ldexp,log,log10,loglp,log2,logb,pow,remainder,remquo,sin,sincos,sinh,sinpi

D

复杂的软件模拟

erf,erfc,fma,lgamma,lgamma_r,pown,powr,rootn,tan,tanpi,tgamma

另外,如果应用程序对精度不敏感的话,开发者可以选择使用内部的或者快速的数学函数来替代标准的数学函数。表8-2 总结了使用数学函数时的3个选项。

  • 使用快速函数时,在调用函数clBuildProgram时使能-cl-fast-relaxed-math。
  • 使用内部的数学函数:
    •   许多函数有内部实现,比如:native_cos, native_exp,native_exp2, native_log, native_log2, native_log10, native_powr,native_recip, native_rsqrt, native_sin, native_sqrt, native_tan ;
    •   下面使用内部数学函数的例子:

原始的:int c = a/b ;// a和b都是整数。

使用内部指令:

int c =(int)native_divide((float)(a)),(float)(b));

表8-2 基于精度/性能的数学函数选择

数学函数

定义

怎么使用

精度要求

性能

典型应用

标准

符合IEEE754单精度浮点要求

默认

严格

科学计算,对精度敏感的情况下

快速

低精度的快速函数

kernel编译选项

-cl-fast-relaxed-math

中等

中等

许多图像,音频和视觉的用例中

内部

直接使用硬件计算

使用native_function替换kernel中的函数

低,与供应商有关

对精度损失不敏感的情况下的图像,音频,和视觉用例中

8.4 循环展开

循环展开通常是一个好方法,因为它能够减少指令执行的耗时从而提高性能。Adreno编译器通常能基于试探法自动地将循环展开。然而,有时候编译器选择不将循环完全展开,因为基于考虑到,寄存器的分配预算,或者编译器因为缺少某些信息不能将它展开等因素。在这些情况下,开发者可以给编译器一个提示,或者手动的强制将循环展开,如下所示:

  • kernel可以使用__attribute__((opencl_unroll_hint))或者__attribute__((opencl_unroll_hint(n))) 给出提示。
  • 另外,kernel可以直接使用#pragma unroll展开循环。
  • 最后一个选择是手动展开循环。

8.5 避免分支

一般地,当在同一个wave中的work item有不同的执行路径时,那么GPU就不是那么高效率。对于某些分支,一些work time必须执行,从而导致较低的GPU使用率,就像图8-1所示。而且,像if-else的条件判断代码通常会引起硬件的控制流逻辑,这个是非常耗时的。

图8-1 绘图表示出现在两个wave中的分支情况

有一些方法可以用来避免或者减少分支和条件判断。在算法层面,一种方法是将进入同一分支的work item组成一个不可分的wave。在kernel层面,一些简单的分叉/条件判断可以转变成快速的ALU操作。在9.2.6节中一个例子中,有耗时的控制流逻辑的一个三元操作被转变成一个ALU操作。其他的方式是使用类似于select函数,这个可能会使用快速的ALU操作来替代控制流逻辑。

8.6 处理图像边界

许多操作可能会获取图像边界外的像素点,比如滤波,变换等。为了更好地处理边界,可以考虑下面的选择:

  • 如果可能的话,对图像进行扩边。
  • 使用带有合适的采样器的image对象(texture引擎会自动处理这个)
  • 编写单独的kernel函数去处理边界,或者让CPU处理边界。

8.7 32位 vs. 64位GPU内存访问

从Adreno A5X GPU开始,64位操作系统逐渐成为主流,而且许多的Adreno GPU支持64位操作系统。64位操作系统中最重要的改变是内存空间将能完全覆盖4GB,而且CPU支持64位指令集。

当GPU可以获取64位内存空间时,它的使用将会引起额外的复杂性,而且可能会影响性能。

8.8 避免使用size_t

64位的内存地址在许多情况下会提升编写OpenCL kernel的复杂度,开发者必须要小心。强烈建议避免在kernels中定义size_t类型的变量。对于64位操作系统,在kernel中定义成size_t的变量可能会被当成64位长度的数据。Adreno GPUs必须使用32位寄存器来模拟64位。因此,size_t类型的变量会需要更多的寄存器资源,从而因为可用的wave变少和更小的workgroup大小导致性能退化。所以,开发者应该使用32位或者更短的数据类型来替代size_t.

对于OpenCL中返回size_t的内嵌函数,编译器会根据它所知道的信息尝试推导并限制数据范围。比如, get_local_id返回的数据类型为size_t,尽管local_id永远不会超过32位。在这种情况,编译器尝试使用一个短的数据类型来替代。但是,更好的方法是,给编译器提供关于数据类型的最充分的信息,然后编译器可以产生更好的优化代码。

8.9 一般的内存空间

OpenCL2.0 介绍了一个新的特性,叫做一般性的内存地址空间,在这个地址空间中,指针不需要指定它的地址空间,在OpenCL2.0之前,指针必须指定它的地址空间,比如指定为是local,private,或者global。在一般性的地址空间中,指针可以动态地被指定为不同的地址空间。

这个特性降低了开发者的代码基础而且能重复使用已经存在的代码,使用一般性的内存地址空间会有轻微的性能损失,因为GPU SP硬件需要动态的指出真正的地址空间。如果开发者清楚知道变量的内存空间,建议清晰地定义内存地址。这将会减少编译器的歧义,从而会有更好的机器代码进而提升性能。

8.10 其他

还有很多其他的优化技巧,这些技巧看起来很小,但是同样可以提高性能,这些技巧如下所示:

  • 已经计算过的数据,而且不会在kernel中被改变的。

    •   如果一个数据可以在外面host端)计算好,那么放到kernel中计算会很浪费。
    •   已经计算好的数据可以通过kernel参数传递给kernel,或者用#define的方式。
  • 使用快速的整型的内嵌函数。使用mul24计算24位的整型乘法,和使用mad24计算24位的整型乘加。

    •   Adreno GPU的内部硬件支持mul24,而32位的整型乘法需要用更多的指令模拟。
    •   如果是在24位范围内的整型数据,使用mul24会比直接使用32位的乘法更快。
  • 减少EFU函数
    •   比如,像r=a/select(c,d,b<T)这样的代码(其中a,b和T是浮点变量,c和d是常数),可以写成r= a * select(1/c,1/d,b<T),这样会避免EFU中倒数函数,因为1/c和1/d可以在编译器编译阶段计算出来。
  • 避免除法操作,特别是整型的除法。

    •   整型的除法在Adreno GPUs上是极其耗时的。
    •   不使用除法,可以使用native_recip计算倒数,像8.3节描述的那样。
  • 避免整型的模操作,这个也很耗时。
  • 对于常数的数组,比如说查找表,滤波tap等,在kernel的外面进行声明。
  • 使用mem_fence 函数来分开或者组合代码段。

    •   编译器会从全局优化的角度,使用复杂的算法产生最优的代码。
    •   mem_fonce 可以用来阻止编译器混排和混合前面或者后面的代码。
    •   mem_fonce 可以让开发者单独操作代码的某个部分来进行优化和调试。
  • 使用位移操作替换乘法。

Qualcomm_Mobile_OpenCL.pdf 翻译-8-kernel性能优化的更多相关文章

  1. Qualcomm_Mobile_OpenCL.pdf 翻译-7 内存性能优化

    内存优化是最重要也是最有效的OpenCL性能优化技术.大量的应用程序是内存限制而不是计算限制.所以,掌握内存优化的方法是OpenCL优化的基础.在这章中,将会回顾OpenCL的内存模型,然后是最优的实 ...

  2. Qualcomm_Mobile_OpenCL.pdf 翻译-9-OpenCL优化用例的学习

    在这一章中,将会用一些例子来展示如何使用之前章节中讨论的技术来进行优化.除了一些小的简单代码片段的展示外,还有两个熟知的图像滤波处理,Epsilon滤波和Sobel滤波,将会使用之前章节中讨论的方法进 ...

  3. Qualcomm_Mobile_OpenCL.pdf 翻译-5-性能优化的概述

    这章提供了一个OpenCL应用程序优化的总体概述.更多的细节将会在接下来的章节中找到. 注意:OpenCL程序的优化是具有挑战性的.相比初始的程序开发工作,经常需要做更多的工作. 5.1 性能移植性 ...

  4. Qualcomm_Mobile_OpenCL.pdf 翻译-4-Adreno OpenCL的程序开发

    这章将简要讨论一些开发Adreno OpenCL应用程序的基本要求,下面将会介绍如何调试和统计程序性能. 4.1  安卓平台上开发OpenCL程序 目前,Adreno GPU主要是在安卓操作系统和在部 ...

  5. [好文翻译]WEB前端性能优化的14条规则

    作为一个半前端工程师,而且只会写点HTML5和CSS3的“假”前端工程师,为了能更好地理解一下前端的花花世界,最近拜读了<高性能网站建设指南>一书,对作者提出的前端性能优化的14个规则获益 ...

  6. Qualcomm_Mobile_OpenCL.pdf 翻译-6-工作组尺寸的性能优化

    对于许多kernels来说,工作组大小的调整会是一种简单有效的方法.这章将会介绍基于工作组大小的基础知识,比如如何获取工作组大小,为什么工作组大小非常重要,同时也会讨论关于最优工作组大小的选择和调整的 ...

  7. Qualcomm_Mobile_OpenCL.pdf 翻译-2

    2  Opencl的简介 这一章主要讨论Opencl标准中的关键概念和在手机平台上开发Opencl程序的基础知识.如果想知道关于Opencl更详细的知识,请查阅参考文献中的<The OpenCL ...

  8. Qualcomm_Mobile_OpenCL.pdf 翻译-3

    3 在骁龙上使用OpenCL 在今天安卓操作系统和IOT(Internet of Things)市场上,骁龙是性能最强的也是最被广泛使用的芯片.骁龙的手机平台将最好的组件组合在一起放到了单个芯片上,这 ...

  9. (翻译) Android ListView 性能优化指南

    本文翻译了Lucas Rocha的Performance Tips for Android’s ListView.这是一篇关于介绍如何提升ListView性能的文章,非常的优秀.使得我拜读之后,忍不住 ...

随机推荐

  1. 1.5 log4j使用教程

    日志是应用软件中不可缺少的部分,Apache的开源项目log4j是一个功能强大的日志组件,提供方便的日志记录.在apache网站:jakarta.apache.org/log4j 可以免费下载到Log ...

  2. 【6】font-size 字体属性

    font-style                         --  字体风格 font-variant                      -- 小型大写字母文本 font-weigh ...

  3. 阶段3 3.SpringMVC·_01.SpringMVC概述及入门案例_05.入门程序之入门代码编写

    先把默认的index.jsp删掉.默认的index.jsp没有jsp的声明 ok webapp文件夹下new一个 起名叫做index.新建的页面有jsp的头 创建控制器类 java下新建一个class ...

  4. linux创建软链接

    linux软链接linux下的软链接类似于windows下的快捷方式实例:ln -s /home/gamestat    /gamestatln -s a b 中的 a 就是源文件,b是链接文件名,其 ...

  5. MySQL orzdba、dodba、top、iostat、vmstat、perf等

    1 mysq自带的mysqladmin命令 mysqladmin -usystem -p*** -h127.0.0.1 -P3306 -r -i 1 extended-status \ |grep & ...

  6. Android 消息传递机制

    线程间消息传递机制 1.消息怎么发送的? 我们都知道当调用Handler发送消息的时候,不管是调用 sendMessage,sendEmptyMessage,sendMessageDelayed还是其 ...

  7. Stream流实现斐波那契数列

    1.前言 我们都知道斐波那契数列有很多种实现方法,在jdk1.8以前没有流操作,只能通过递归或者迭代等其他方式来实现斐波那契数列, 但是jdk1.8以后,有了流操作,我们就可以使用流来实现斐波那契数列 ...

  8. lnmp 环境下 部署 laravel 项目

    出现错误 Warning: require(): open_basedir restriction in effect. File(/xxxx/vendor/autoload.php) is not ...

  9. Golang基础(7):go的net/rpc用法

    一:PRC是什么? RPC(Remote Procedure Call) 远程过程调用,是一个计算通信协议.该协议允许一台计算机上的程序调用另外一台计算机上的程序.远程过程调用就是2个不在同一台计算机 ...

  10. 【VS开发】windows注册ActiveX控件

    ActiveX控件是一个动态链接库,是作为基于COM服务器进行操作的,并且可以嵌入在包容器宿主应用程序中,ActiveX控件的前身就是OLE控件.由于ActiveX控件与开发平台无关,因此,在一种编程 ...