这章将简要讨论一些开发Adreno OpenCL应用程序的基本要求,下面将会介绍如何调试和统计程序性能。
4.1 安卓平台上开发OpenCL程序
目前,Adreno GPU主要是在安卓操作系统和在部分Linux系统上支持OpenCL。为了开发带OpenCL的安卓app,开发者必须熟悉android软件开发套件(SDK)和本地开发套件(NDK 用来运行C/C++的)。更多关于Android SDK和NDK的信息,可分别参考https://developer.android.com/index.html和https://developer.android.com/ndk/index.html。
在这章和接下来的章节中,我们假设是在Android平台上进行开发,并且开发者对Android SDK和NDK有相关经验。基于Linux的app开发与之类似。
下面是关于在骁龙平台上开发OpenCL的一些必要条件:
- 支持OpenCL的骁龙设备。并不是所有的骁龙设备都支持OpenCL。可参考表3-1获取更多细节。
- OpenCL软件。在Adreno GPUs上的OpenCL需要依赖QTI合适的库文件。
- 检查设备上是否安装了OpenCL库。核心库是libOpenCL.so,通常会放在设备的/vendor/lib目录下。
- 一些生产商会选择并不包含OpenCL软件(比如Google的Nexus和Pixel devices)。
- OpenCL必须运行在NDK层。
- 对于开发和测试来说,root访问权限并不是必须的,不过如果需要使用高性能模式在SOC上运行,就会需要root权限。
表4-1在Adreno GPUs上开发OpenCL的条件
条目
要求
备注
设备
Adreno A3x/A4x/A5x GPUs
操作系统
安卓,Linux
只有某些Linux平台支持OpenCL
需要的设备软件
设备上的libOpenCL.so
可能有些设备上没有
开发要求
Adreno NDK/SDK
OpenCL代码需要运行在NDK层上
设备的root访问权限
通常不需要
在高性能模式下需要
4.2 调试工具
由于GPU的并行执行特点,调试OpenCL核通常是比较具有挑战的。Adreno GPUs支持在kernel函数内部调用printf函数,这个对于调试来说非常有用。使用printf时,建议减少工作负载,因为printf会减慢代码的执行,输出有条件限制的变量从而避免打印出太多次。比如说,可能只使能一个有问题的workgroup,甚至是单个有问题的work item(通过在CLEnqueueNDRangeKernel中设置合适的偏移)。
知道设备的软件版本号是很重要的,因为某些错误或者问题或许已经在新版本中解决了。使用API函数clGetDeviceInfo,可以查询软件(驱动)和编译器的版本。可查看参考文献获取更多信息。
4.3 骁龙分析器(Snapdragon Profiler)
骁龙分析器是由QTI提供的一个统计分析工具,可以运行在Windows,Mac和Linux平台,能够让开发者分析在Android上运行的骁龙处理器的一些信息,包括CPU,GPU,DSP,内存,功率,热量,网络数据等。它支持OpenCL和许多图像的APIs,比如OpenGL ES和Vulkan。为了获取更多细节,请参考https://developer.qualcomm.com/software/snapdragon-profiler.
下面是骁龙分析器针对OpenCL进行统计分析而提供的关键特性:
1. 分析器有一个kernel分析器,可以对一个指定的kernel进行静态分析。它会提供比如寄存器占用空间,指令总数和每一种类型指令的数量等信息,这些可以帮助开发者更好的优化kernels。
2. 分析器会对指定的OpenCL应用程序提供OpenCL 的API调用轨迹和日志。这个功能可以让开发者从API层面就发现和解决瓶颈,同样也可以调试程序(通过查看调用轨迹和日志)。
3. 分析器会提供一些信息,比如GPU繁忙率,ALU使用率,L1/L2 cache命中率等,这些是开发者分析kernel函数性能问题的基本信息。
4. 分析器支持基于命令行的应用程序,同样支持Android GUI app。
4.4 性能统计
给定一个app,准确统计他的性能是很困难的。GPU计时和GPU计时是两个常用的方法,它们的不同点将会在接下来的章节中讨论。
4.4.1 CPU计时
CPU计时是用来测量在主设备端调用OpenCL的完整执行时间。可以通过使用标准的C/C++语言库中提供的任何日期或者时间的函数来实现。下面是一个使用gettimeofday的例子。
#include <time.h>
#include <sys/time.h>
void main() {
struct timeval start, end;
gettimeofday(&start, NULL); /*get the start time*/
/*Execute function of interest*/ { . . .
clFinish(commandQ);
}
gettimeofday(&end, NULL); /*get the end time*/
/*Print the total execution time*/
printf("%ld\n", ((end.tv_sec * 1000000 + end.tv_usec)
- (start.tv_sec * 1000000 + start.tv_usec)));
}
OpenCL的实时运行队列API函数可以分为阻塞调用和非阻塞调用。对于非阻塞调用,使用CPU计时需要考虑以下几点:
- 非阻塞调用意味着,主设备提交完任务(任务通常是放在另外一个CPU线程中排队等待执行)后,就会执行下一条指令,而不是等待函数执行完毕。
- kernel执行的API函数,clEnqueueNDRangeKernel,是一个非阻塞函数。
- 对于非阻塞调用,真正的执行时间并不是函数调用的前后时间差。
当使用一个CPU计时器在host端测量kernel执行时间的时候,必须保证这个函数完全执行完,可以通过使调用clWaitforEvent(如果针对非阻塞调用有一个eventID)或者clFinish保证。同样的规则适用于调用内存交换函数的时候。
4.4.2 GPU计时
所有的OpenCL队列函数的调用,可以有选择的向host返回一个事件对象,OpenCL的性能统计API函数可以使用这个事件对象来查询执行时间。Adreno GPUs用它们自己的时钟和计时器来测量函数执行流程,并且GPU的执行时间是由GPU硬件计数器决定的,与操作系统无关。
要使能GPU计时器的功能,需要对当前的命令队列,在函数clCreateCommandQueue或者clSetCommandQueueProperty中合适位置设置标志CL_QUEUE_PROFILING_ENABLE。而且,必须为队列函数提供一个事件对象。一旦函数执行完,可以使用API函数clGetEventProfilingInfo来获取命令队列的性能统计信息。
对于一个clEnqueueNDRangeKernel调用,使用clGetEventProfilingInfo函数并设置4个统计参数,包括CL_PROFILING_COMMAND_(QUEUED , SUBMIT, START,和END)后,可以提供一个在Andreno GPU kernel启动延迟和kernel执行时间的精确的快照,如图4-1所示。
- 前两个参数CL_PROFILING_COMMAND_(QUEUED和SUBMIT)之间的差,给出软件的开销和CPU cache 操作的开销。OpenCL软件可能选择一个kernel先加入队列,然后与接下来入队的其他kernel一起提交,如果队列中的kernel函数足够多。开发者可以使用clFlush来加速提交。
- CL_PROFILING_COMMAND_(SUBMIT和START)的差可以给出GPU正在运行的其他工作的时间。
- CL_ PROFILING _COMMAND_(START和END)之间的差就是kernel在GPU上的运行时间。
开发者主要就是缩小实际的kernel的运行时间。对于其他两个比较难控制的时间来说,这个的提升相对简单些。
图4-1 在Adreno GPUs中clEnqueueNDRange中的性能统计标志
4.4.3 GPU计时 vs. CPU计时
GPU和CPU计时器都可以用来统计性能性能,那对于应用程序来说使用哪一个来呢?尽管GPU计时器能能够精确的测量出GPU执行时间,但是一些硬件操作时间(比如cache 刷新)和一些软件操作(比如CPU和GPU的同步)是不在GPU的时钟系统中的。所以,对于kernel执行,GPU计时统计的性数据看起来比CPU计时统计的要好。下面是两个实际操作中的建议:
- GPU计时应该用来衡量kernel的优化效果。GPU计时能够准确的统计出,从GPU执行的角度,通过这些优化的步骤能提升了多少性能。
- CPU计时应该用来测量整个程序的端对端的性能(就是整个程序的性能)。如果OpenCL程序是整个应用程序流水线的一部分,那应该使用CPU计时来统计。
4.4.4 高性能模式
骁龙的SOC拥有先进的动态时钟和电源管理机制,这种机制能够自动控制系统使系统在特定的场景中运行在低功耗模式下,这种模式能够减少耗电。通常情况下,如果有高强度的工作负荷,系统会自动提升时钟频率和电压,使得设备进入所谓的高性能模式,从而提升性能和满足高工作负荷。
对于OpenCL优化,如果系统动态调整了时钟频率,理解和统计性能是十分困难的。因此,为了性能的精度和一致性,建议使能高性能模式。
如果没有设置高性能模式,在一系列的OpenCLkernel中,第一个kernel函数通常会表现出更长的启动延迟和更慢的执行时间。所以,必须在启动真正的GPU任务之前,需要用一些简单的kernel热身GPU。
一个OpenCL kernel的性能不是只依赖于GPU。在CPU上运行API函数跟在GPU上执行的kernel一样重要。所以,为了达到最佳性能,CPU和GPU都需要使能高性能模式。另外,为减少来自UI渲染的接口调用,建议:
- 保证当前正在统计的性能的应用程序已经渲染了整个屏幕,这样其他的动作不能再更新屏幕。
- 如果是一个内部的程序,保证SurfaceFlinger并没有在安卓上运行。这样就能保证,只有正在被统计性能的应用函数在使用CPU和GPU。
对于A3x, A4x和 A5x GPU,使能高性能模式的一系列命令有很大的区别。可参考Section A获取更多细节。
4.4.5 GPU的频率控制
应用程序可以使用cl_qcom_perf_hit扩展功能来控制GPU频率。当创建OpenCL上下文时,这个扩展功能允许应用程序设置一个性能提示属性。性能的等级可以使HIGH,NORMAL和LOW.NORMAL性能等级会使能动态的时钟和电压控制。HIGH和LOW性能等级将会禁止这种动态控制,强制GPU分别运行在最高和最低频率上。
注意:性能等级仅仅是一个提示。驱动会尝试遵循这个提示,但是,其他一些因素,比如热量控制,或者外部的应用程序或服务都可以否决这个提示。这个性能提示的扩展功能给了应用程序一些平衡功率/性能弹性。但是,必须要小心的使用,因为这个对SOC层的功耗消耗有很大的影响。