Qualcomm_Mobile_OpenCL.pdf 翻译-5-性能优化的概述
这章提供了一个OpenCL应用程序优化的总体概述。更多的细节将会在接下来的章节中找到。
注意:OpenCL程序的优化是具有挑战性的。相比初始的程序开发工作,经常需要做更多的工作。
5.1 性能移植性
就像在2.4.2节中讨论的那样,在不同的架构之间,OpenCL一般都没有很好的性能移植性。针对某一个平台,特别是针对某个GPU优化的OpenCL应用程序,移植到Adreno GPU上后可能没有相同的性能。编程指南和其他OpenCL厂商的最佳做法,可能对Adreno GPU完全不适用。因此,针对在Adreno GPU上的优化,通读整个文档是非常重要的。此外,针对一种Adreno GPU优化的应用程序可能需要经过部分调整或者优化,才能在其他系列的Adreno GPU上达到最佳性能。
5.2 优化的总体视角
优化一个OpenCL的应用程序可以简单的分为一下三个级别,从高到低:
- 应用程序/算法
- API 函数
- kernel 函数
一个OpenCL优化问题本质上就是如何最优的使用内存带宽和计算能力,包括:
- 以最优方式使用全局内存,本地内存,寄存器和cache。
- 以最优的方式发挥计算资源的作用,比如ALU和texture操作。
这章接下来的部分将会集中在应用程序级别的优化。其他层的优化将会在接下来的章节中讨论。
5.3 对使用OpenCL进行初始的评估
在盲目使用OpenCL之前,开发者需要先判断当前的应用程序是否适合用OpenCL优化。下面是一些适合在GPU上加速的程序的典型特点:
- 大量的输入数据
- 对于少量的输入数据,CPU和GPU之间的开销可能抵消掉了OpenCL优化带来性能提升。
- 计算密集
- GPU拥有很多计算单元,而且他最高的计算能力,gflops,通常比CPU高出很多。为了充分利用GPU,应用程序需要有许多复杂的计算。
- 适合并行化计算
- 工作任务可以被划分为互相独立的小单元,每一个小单元任务的处理并不会影响其他的单元任务。
- 需要使用并行化任务充分利用GPU的隐藏内存延迟的能力,这是GPU最关键的一个能力。
- 有限的分支控制流
- GPU并没有像CPU那样,设计地能够处理有效的分支控制。如果使用了大量的条件判断和分支操作,CPU可能会更合适。
5.4 将CPU代码移植到GPU OpenCL
通常情况下,对于需要转成OpenCL的代码,开发者可能已经有一个基于CPU版本的参考程序。假设这个程序包含了许多小的功能模块。将每个模块分别对应一个OpenCL kernel函数,这样看起来很方便,但是,这种情况下的性能可能不是最优的。需要考虑一下几个事实:
- 在某些情况下,将CPU的几个功能模块合并成一个OpenCL函数可能会有更好的性能,如果合并能减少GPU和内存之间的数据流量。
- 在某些情况下,将一个复杂的CPU功能函数模块分解成几个小的简单的OpenCL kernel,可能会对单个的kernel有更好的并行性和对整体有更好的性能。
- 开发者可能需要调整数据结构来适合新的数据流,这种新的数据流方式可以减少整体的数据量。
5.5 GPU和CPU任务的并行
为了充分使用SOC的计算性能,当GPU执行一个kernel函数时,应用程序可能会将指定的任务分配到CPU上。当设计这种结构和分配任务时,下面是需要考虑的几点:
- 让CPU运行适合在CPU上运行的部分,比如分支控制和顺序操作。
- 避免出现GPU空闲等待CPU执行完成的情况,或者相反情况。
- CPU和GPU之间的数据共享很耗时。所以,试着将一些轻量的CPU任务分配给GPU,尽管这些任务并不是合适GPU,这样是为了避免数据传输。
5.6 瓶颈分析
识别和分析瓶颈是至关重要的,因为这会使注意力集中到需要优化的区域。瓶颈导致拖延而且经常是应用程序中最慢的部分。不管其他的部分是多么有效率,应用程序的整体性能将会被最慢的那个部分限制,比如瓶颈部分。在瓶颈解决之前,关注其他部分是没有意义的。
5.6.1 识别瓶颈
通常情况,一个kernel要么是内存限制要么是计算限制(也可以说是ALU限制)。一个简单判别技巧是,按如下方式操作kernel代码并将它运行到设备上:
- 如果增加许多计算并不改变性能,那么这不是计算限制。
- 如果加载大量的数据并不改变性能,那么这不是内存限制。
在4.3节中讨论的骁龙profiler也可以用来识别瓶颈。
5.6.2 解决瓶颈
一旦一个瓶颈被确认了,可以使用不同的策略来解决它:
- 如果是一个ALU计算瓶颈的问题,找到方法减少计算复杂度和计算次数,比如在精度要求不高的情况下啊,使用更快的数学函数和内嵌的数学函数,或者使用16位浮点数代替32位浮点数。
- 如果是一个内存瓶颈的问题,试着提升内存访问效率,比如并行访问/存储,利用本地内存,或者texture cache(比如,用只读的image对象替代缓冲区对象)。使用更短的数据类型来实现在GPU和全局内存中之间存储/装载,这样能够节省内存流量。
细节的问题将会在接下来的章节中讨论。
注意:随着优化的进展,瓶颈可能会改变。如果内存限制被解决了,内存限制就会变成ALU限制,或者反之。为了获取最佳的性能,需要进行许多来来回回的迭代。
5.7 API层面的性能优化
OpenCL的API函数是执行在CPU端的,主要是管理资源和控制程序的运行。尽管,一般来说,在计算复杂度方面API函数相对于kernel的执行是很小的,但是API函数不恰当使用将会带来巨大的性能损失。下面是一些建议,能够帮助开发者避免一些常见的陷阱。
5.7.1 合理安排API函数的调用
耗时的API函数应该放在合适的位置上,避免他们阻塞或者影响GPU上的启动工作。一些OpenCL API函数需要耗费很长的时间去执行,所以必须在执行的循环外面调用。比如,下面的函数将会消耗大量的时间执行。
clCreateProgramWithSource()
clBuildProgram()
clLinkProgram()
clUnloadPlatformCompiler()
为了减少在应用程序启动阶段的执行时间,使用clCreateProgramWithBinary来替代clCreateProgramWithSource。可以参考5.7.3章节获取更多信息。
注意:如果clCreateProgramWithBinary失败,不要忘记返回然后重新编译源码。坦白来着,这种情况会经常发生,如果OpenCL软件进行了不兼容的更新。
- 避免在NDRange调用之间,创建和释放内存对象。因为clCreate{Image|Buffer}的执行时间和请求内存的大小有关系(如果使用了host_ptr的话)。
- 如果可能,使用Android ION的内存分配。clCreate{Buffer|Image2D}会使用一个ION指针来创建内存对象,而不是分配新内存然后进行拷贝。章节7.4中讨论了如果使用ION内存。
- 在OpenCL中,尝试重复使用内存和上下文对象,避免创建新的对象。总的来说,host端需要做一些轻量级的工作,在启动GPU kernel的时候,避免阻塞GPU的执行。
5.7.2 使用事件驱动的流水线方式
OpenCL中入队的API函数可能会接收一个事件列表的参数,这个参数表示在当前的API函数开始执行之前,列表中的所有的事件必须执行完。同时,这个API函数同样可以产生一个时间ID来识别他们自己。如果事件列表参数正确的表示了依赖关系,那么host端只需简单地将API函数和kernel提交给GPU执行,而不需要操心他们之间的依赖关系和完成情况。通过这种方法,启动一个API函数的调用开销将会显著减少,因为软件能够按照最优方式去调度这些函数并且host端不需要在API函数调用之间进行连接(换句话说,就是不需要调用完一个后,host等待他执行完再调用另一个API函数)。因此,通过使用事件驱动的方式使得API函数的执行像流水线的方式,这种方法是非常推荐的。另外,开发者主要注意:
- 避免阻塞的API调用。一个阻塞的调用会是CPU停下来等待GPU执行完成,进而在下一次的clEnqueueNDRangeKernel的调用之前阻塞了GPU。阻塞API调用通常用在调试过程中。
- 使用回调函数。从OpenCL1.2开始,对许多API函数进行了增强和修改,API函数能够接受自定义的回调函数去处理事件,而且因为host端能够更灵活的处理事件,这种异步的调用机制会使流水线更有效地执行。
5.7.3 kernel的装载和编译
实时的装载和编译kernel源码是非常耗时的。因为一些参数可能无法提前获取,所以一些应用程序宁愿运行过程中编译源码。如果生成和编译源码并不影响GPU执行,那么这是可行的。但是,一般情况下,不建议动态地生成源码。
取代实时编译源码,一个更好的方式是离线编译源码,然后直接使用二进制kernel。当应用程序装载时,二进制的kernel代码也同样被装载。使用这个将会显著降低从磁盘中装载代码的开销。
如果应用程序是用在不同系列的骁龙设备上,那么就需要不同的版本的二进制代码。考虑到兼容性问题,需要注意以下几点:
- 针对某一种GPU编译的二进制的代码只能在该GPU上使用。如果一个二进制是在Adreno A530的GPU的设备上编译的,那么这个二进制代码不能被用在Adreno A540的GPU上。
- 在编译器版本之间,向后的兼容性是可以达到的。新版本的编译一般会支持旧版本的二进制,不过目标GPU是要一样的。
如果发现了一个不兼容的二进制kernel,使用clCreateProgramWithSource作为一个备用解决方法。
5.7.4 使用有顺序的命令队列
Adreno OpenCL平台支持乱序的命令队列。然而,在实施乱序的命令队列时需要进行依赖之间的管理,这样会导致很大的开销。Adreno软件流水命令可以发出一个顺序队列。因此,使用顺序的命令队列是比使用乱序的更好的一种的选择。
Qualcomm_Mobile_OpenCL.pdf 翻译-5-性能优化的概述的更多相关文章
- Qualcomm_Mobile_OpenCL.pdf 翻译-8-kernel性能优化
这章将会说明一些kernel优化的小技巧. 8.1 kernel合并或者拆分 一个复杂的应用程序可能包含很多步骤.对于OpenCL的移植性和优化,可能会问需要开发有多少个kernel.这个问题很难回答 ...
- nginx 性能优化的概述及在CPU资源方面的处理
nginx的性能优化的概述 软件层面的提升硬件的使用率 增大CPU的利用率 增大内存的利用率 增大磁盘IO利用率 增大网络带宽利用率 提升硬件规格 网卡:万兆网卡.例如10G.25G.40G等 磁盘: ...
- Qualcomm_Mobile_OpenCL.pdf 翻译-7 内存性能优化
内存优化是最重要也是最有效的OpenCL性能优化技术.大量的应用程序是内存限制而不是计算限制.所以,掌握内存优化的方法是OpenCL优化的基础.在这章中,将会回顾OpenCL的内存模型,然后是最优的实 ...
- Qualcomm_Mobile_OpenCL.pdf 翻译-9-OpenCL优化用例的学习
在这一章中,将会用一些例子来展示如何使用之前章节中讨论的技术来进行优化.除了一些小的简单代码片段的展示外,还有两个熟知的图像滤波处理,Epsilon滤波和Sobel滤波,将会使用之前章节中讨论的方法进 ...
- Qualcomm_Mobile_OpenCL.pdf 翻译-6-工作组尺寸的性能优化
对于许多kernels来说,工作组大小的调整会是一种简单有效的方法.这章将会介绍基于工作组大小的基础知识,比如如何获取工作组大小,为什么工作组大小非常重要,同时也会讨论关于最优工作组大小的选择和调整的 ...
- Qualcomm_Mobile_OpenCL.pdf 翻译-2
2 Opencl的简介 这一章主要讨论Opencl标准中的关键概念和在手机平台上开发Opencl程序的基础知识.如果想知道关于Opencl更详细的知识,请查阅参考文献中的<The OpenCL ...
- Qualcomm_Mobile_OpenCL.pdf 翻译-1
1 前言 1.1 目的 这篇文档的主要目的是,向原始设备制造商(OEMs),独立软件供应商(ISVs),第三方开发者们,提供在基于高通骁龙400系列.600系列,和800系列的手机平台和芯片上进行开发 ...
- Qualcomm_Mobile_OpenCL.pdf 翻译-10-总结
这篇文档主要是介绍了关于在Adreno GPUs上优化OpenCL代码的详细方法.文档中提供的大量信息能够帮助开发者理解OpenCL基础和Adreno结构,还有最重要的,掌握OpenCL优化技能. O ...
- Qualcomm_Mobile_OpenCL.pdf 翻译-4-Adreno OpenCL的程序开发
这章将简要讨论一些开发Adreno OpenCL应用程序的基本要求,下面将会介绍如何调试和统计程序性能. 4.1 安卓平台上开发OpenCL程序 目前,Adreno GPU主要是在安卓操作系统和在部 ...
随机推荐
- 怎么理解一个规模大且结构复杂的c工程源码
很久以前,当要着手一个规模很大,结构复杂的c工程源码时,总是感觉无从下手.这个时候,一般google一下”XX源码分析“.当这个源码是很广泛使用的时,这样到也能得到不少启发:很不幸,经常要接触一些很少 ...
- leetcode 496下一个更大的元素I
单调递减栈来做,time O(n),spaceO(n)需要一个哈希map class Solution { public: vector<int> nextGreaterElement(v ...
- while循环嵌套
<1>while嵌套的格式 while 条件1: 条件1满足时,做的事情1 条件1满足时,做的事情2 条件1满足时,做的事情3 ...(省略)... while 条件2: 条件2满足时,做 ...
- 从txt导入数据到mysql
当要往mysql的table中录入数据量大的时候,直接从txt录入已有数据是一个愉快的选择. 在录入数据前要做一些格式上的准备 1. txt编码要是utf-8,无BOM 2. 每行以\t\n结尾,每列 ...
- opengl入门篇二: 索引缓冲对象EBO
在绘制图形的过程中,顶点可能会重复.比如两个三角形组成了四边形,那么,必然有两个点是重复的.因此采用索引的方式,四个点即可描述四边形. // 四个顶点 GLfloat vertices[] = { / ...
- Selenium 2自动化测试实战9(简单元素操作)
一.简单元素操作 1. webdriver中常用的几个方法: clear():清除文本 send_keys(*value):模拟按键输入 click():单击元素 clear()方法用于清除文本输入框 ...
- 【DVWA】SQL Injection(SQL 注入)通关教程
日期:2019-07-28 20:43:48 更新: 作者:Bay0net 介绍: 0x00.基本信息 关于 mysql 相关的注入,传送门. SQL 注入漏洞之 mysql - Bay0net - ...
- Python文件重命名代码
import os def re_name(path): for file in os.listdir(path): file_path = os.path.join(path, file) # 判断 ...
- VirtualBox-5.2.8-121009-Win,虚拟机指令ifconfig不显示ip解决方法
- Unity 动画属性
在动画的使用上使用不当的设置往往会造成不可预料的结果. 首先,如果动画自身可以驱动物体移动,那么在Animator组件上必须选择apply root motion,物体的动画位移才能生效,否则动画只能 ...