这章提供了一个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-性能优化的概述的更多相关文章

  1. Qualcomm_Mobile_OpenCL.pdf 翻译-8-kernel性能优化

    这章将会说明一些kernel优化的小技巧. 8.1 kernel合并或者拆分 一个复杂的应用程序可能包含很多步骤.对于OpenCL的移植性和优化,可能会问需要开发有多少个kernel.这个问题很难回答 ...

  2. nginx 性能优化的概述及在CPU资源方面的处理

    nginx的性能优化的概述 软件层面的提升硬件的使用率 增大CPU的利用率 增大内存的利用率 增大磁盘IO利用率 增大网络带宽利用率 提升硬件规格 网卡:万兆网卡.例如10G.25G.40G等 磁盘: ...

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

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

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

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

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

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

  6. Qualcomm_Mobile_OpenCL.pdf 翻译-2

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

  7. Qualcomm_Mobile_OpenCL.pdf 翻译-1

    1 前言 1.1 目的 这篇文档的主要目的是,向原始设备制造商(OEMs),独立软件供应商(ISVs),第三方开发者们,提供在基于高通骁龙400系列.600系列,和800系列的手机平台和芯片上进行开发 ...

  8. Qualcomm_Mobile_OpenCL.pdf 翻译-10-总结

    这篇文档主要是介绍了关于在Adreno GPUs上优化OpenCL代码的详细方法.文档中提供的大量信息能够帮助开发者理解OpenCL基础和Adreno结构,还有最重要的,掌握OpenCL优化技能. O ...

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

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

随机推荐

  1. CentOS 6.4 搭建 ntop 网络流量监控分析平台

    [前言] Ntop是一种监控网络流量工具,用ntop显示网络的使用情况比其他一些网络管理软件更加直观.详细.Ntop甚至可以列出每个节点计算机的网络带宽利用率. 功能: 自动从网络中识别有用的信息: ...

  2. P2010 回文日期

    P2010 回文日期 题解 回文日期,一共8位,只需要枚举4位就好了其实,然后判断它是否合法 代码 #include<bits/stdc++.h> using namespace std; ...

  3. 实现一个可以用 await 异步等待的 Awaiter

    总结起来,要想使一个方法可被 await 等待,必须具备以下条件: 这个方法返回一个类 A 的实例,这个类 A 必须满足后面的条件.此类 A 有一个可被访问到的 GetAwaiter 方法(扩展方法也 ...

  4. 代码代码:输入两个正整数m和n,求其最大公约数和最小公倍数。15 20 5

    import java.util.Scanner; //输入两个正整数m和n,求其最大公约数和最小公倍数.15 20 5 public class Test { public static void ...

  5. 【3】火狐中: radio被点击以后,重刷页面,不会选择默认的radio

    1.问题:火狐中radio (单选框)点击以后,重新刷新页面,不会选择默认的radio 解决:form表单中添加:autocomplete="off" autocomplete 属 ...

  6. ControlTemplate in WPF —— Menu

    <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" x ...

  7. ubuntu/work

    1.Ubuntu16.04 创建桌面快捷方式 cd /usr/share/applications vim goland.desktop ##add content [Desktop Entry] E ...

  8. 深入理解java:2.4. 线程本地变量 java.lang.ThreadLocal类

    ThreadLocal,很多人都叫它做线程本地变量,也有些地方叫做线程本地存储,其实意思差不多. 可能很多朋友都知道ThreadLocal为变量在每个线程中都创建了一个副本,那样每个线程可以访问自己内 ...

  9. neo4j - 查询效率的几种优化思路

    最近在公司实习做的就是优化neo4j图形数据库查询效率的事,公司提供的是一个在Linux上搭建且拥有几亿个节点的数据库.开始一段时间主要是熟悉该数据库的一些基本操作,直到上周才正式开始步入了优化数据库 ...

  10. 思考--mysql 分库分表的思考

    查询不在分库键上怎么办,扫描所有库?由于分库了,每个库扫描很快?所以比单个表的扫描肯定快,可以这样理解吗. 多表jion怎么弄,把内层表发给每个分库吗? citus,tidb 都有这些问题,citus ...