Mobile Heterogeneous Computing Technology-GPU OpenCL Programming (Advanced)

picture

Introduction : This article will explain some in-depth knowledge of OpenCL, and at the same time, combined with the chip design of Qualcomm Adreno, one of the mainstream GPU manufacturers on the mobile terminal, to explain some general methods for optimizing OpenCL programming on the mobile terminal. The full text is 5201 words, and the estimated reading time is 14 minutes.


I. Introduction

In "Mobile Heterogeneous Computing Technology - GPU OpenCL Programming (Basic)" , the current situation of mobile GPU computing and the basic concepts of OpenCL programming are introduced. This article will further elaborate some in-depth knowledge of OpenCL, and at the same time, combined with the chip design of Qualcomm Adreno, one of the mainstream GPU manufacturers on the mobile terminal, to explain some general methods for optimizing OpenCL programming on the mobile terminal.
Remarks: In addition to the Qualcomm GPU series, Huawei Kirin, MediaTek Tianji and other chips use the Mali series GPU designed by ARM, and this article will not explain it separately due to space limitations.


2. Basic Concepts

OpenCL

OpenCL is an open, free standard developed and maintained by the Khronos organization for cross-platform parallel programming in heterogeneous systems. This design can help developers use modern heterogeneous systems to exert powerful parallel computing capabilities, while also being cross-platform to a certain extent.

OpenCL Qualcomm

Qualcomm is one of the first chip manufacturers to fully support the computing power of OpenCL on the mobile side, and has occupied a certain market share in the domestic and international markets.


3. OpenCL structure

picture

An abstract OpenCL application usually includes the following parts:

  1. CPU Host: As the manager and scheduler of the entire OpenCL application, it controls the execution process of the entire OpenCL.

  2. OpenCL Devices: Specific OpenCL hardware devices, such as GPU, DSP, FPGA, etc.

  3. OpenCL Kernels: The OpenCL kernel code that undertakes the task will be compiled by the OpenCL Host and executed on the corresponding hardware.

OpenCL for mobile devices

目前的经验来看,Android阵营中的移动端设备,OpenCL通常使用GPU作为硬件加速端。高通的建议是在移动端选择GPU作为OpenCL的加速设备(注:其实多数情况并无选择可能,有且只能获得到一个GPU的device)。


四、OpenCL兼容性

程序可移植性

OpenCL提供了不错的程序兼容性,一套OpenCL的代码,在不同的设备上都可以正常运行。当然,少部分基于硬件拓展能力,取决于当前硬件的支持情况。

性能可移植性

与程序兼容性不同,OpenCL的性能可移植性通常是比较差的。作为一种高级的计算标准,OpenCL硬件部分的实现是依赖厂商的,每个厂商都会有各自的优点和缺点。因此,针对不同的硬件平台,如高通Adreno 或者Arm Mali,同样的代码的性能表现是不同的。即使是相同的产商,随着硬件的迭代,相应的驱动也会有对应的微调,以充分利用新一代硬件的全部能力。针对不同的设备或者硬件针对性优化是非常必要的。当然这个是一个ROI(投入回报)问题。

向后兼容性

OpenCL的设计尽可能的保证向后兼容性。如果要使用已经过时的能力的话,只需要引入特定的头文件即可。值得注意的是:OpenCL的拓展是不完全向后兼容的,这些拓展通常由硬件厂商结合硬件特性来提供,因此应用时需要考虑到不同硬件之间的拓展兼容性。


五、高通 Adreno OpenCL架构

picture

图中为高通Adreno GPU OpenCL(Adreno A5x GPUS)上层架构,OpenCL在执行过程中涉及到几个关键的硬件模块。

Shader (or streaming) processor (SP) (着色器、流处理器)

  • Adreno GPU的核心模块,包含众多硬件模块,如算数逻辑单元、加载存储单元、控制流单元、寄存器文件等。

  • 运行图形着色器(如顶点着色器、片元着色器、计算着色器等),运行计算负载,如OpenCL内核等。

  • 每个SP对应一个或多个OpenCL的运算单元。

  • Adreno GPU可能包含一个或者多个SP,取决于芯片的档次,上图中展示的是单个SP的情况。

  • SP加载和读取Buffer类型或者带有__read_write标记的Image类型数据对象时,可以利用L2缓存。

  • SP加载只读的Image类型的数据对象时,可以利用L1缓存或者纹理处理器。

Texture Processer (TP) (纹理处理器)

  • 根据内核的调度来进行纹理操作,如纹理的读取、过滤等。

  • TP和L1缓存相结合,减少从L2缓存中读取数据时的缓存丢失几率。

Unified L2 Cache (UCHE) (统一L2缓存)

  • 响应SP对于Buffer类型的读取和加载,以及L1对于Image类型的数据的加载操作。


六、如何编写高性能OpenCL代码

性能兼容性

前文提及了OpenCL的性能兼容性,由于不同硬件的特性并不相同,因此在一块芯片上的调优后的OpenCL代码在另一块芯片上性能可能并非最优的。需要参考对应硬件的文档来进行特异性的优化工作。对于不同的芯片,针对性的优化是必要的。

手段总览

OpenCL程序的优化通常可以分为以下三类:

  • 程序、算法级别优化

  • API级别优化

  • OpenCL内核优化

程序算法以及API层级的优化手段是较为通用的,此处主要展开OpenCL内核的优化手段。

OpenCL的优化问题本质上一个如何利用内核带宽和计算能力的问题。即合理的利用全局内存、本地内存、寄存器、多级缓存等,以及合理的利用逻辑运算单元、纹理单元等等。

程序是否适用OpenCL

开发者需要确定程序是否适合使用OpenCL编写,可以通过以下几个方面来判断:

  • 是否存在较大的数据输入

  • 程序本身是否是计算密集型

  • 程序是否对并行计算亲和

  • 程序中的控制流操作相对较少

将CPU代码改造为GPU代码时性能Tips

明确了上述的几个关键点之后,开发者可以着手将CPU的代码转化为OpenCL的代码,为了达到一个最优的性能,需要关注以下几个方面:

  • 一些情况下,将多个CPU的操作合并到一个OpenCL内核当中可以得到性能收益。这个方式通常适用于减少GPU和主存之间的内存拷贝。

  • 一些情况下,将一个复杂的CPU程序拆分成几个简单的OpenCL内核,可以得到更好的程序并行性,进而达到全局性能最优。

  • 开发者需要考虑重新设计整体的数据架构,便于减少数据传递的开销。

这些情况要结合实际的情况进行考量,通常也是高性能异构编程本身的难点所在。

并行化CPU和GPU的工作流

充分的利用芯片的计算性能,应当合理的规划任务,在GPU执行一些计算工作的同时,CPU也可以同时承担部分工作。通常可以总结为以下几点:

  • 使CPU去执行CPU善于执行的部分,比如分支控制逻辑,以及一些串行的操作。

  • 尽可能避免GPU进入闲置状态,等待CPU下达进一步任务的情况。

  • CPU和GPU之间的数据传递成本极高,为了减少这部分成本,可以将一些本身适合CPU进行的任务放到GPU进行。


七、性能分析

性能Profile

可以结合Profile手段来分析程序性能。由于OpenCL程序分为宿主的CPU的调度逻辑,以及GPU硬件上的执行逻辑。开发者可以分别从CPU调度流程以及GPU执行两个层面去进行性能的Profile。通常CPU Profile是用来衡量整个流程端到端的性能,GPU Profile用来衡量OpenCL内核性能

CPU Profile

可以采用标准的c++编程方式,例如通过 gettimeofday 之类的api去进行CPU流程间的时间统计。
本文中列出部分示例代码,详细demo可参考OpenCL Profile(github.com/xiebaiyuan/…

#include <time.h>
#include <sys/time.h>
void main() {
    struct timeval start, end;
    // get the start time
    gettimeofday(&start, NULL); 
    // execute function of interest
    {
        . . .
        clFinish(commandQ);
    }
    // get the end time
    gettimeofday(&end, NULL); 
    // Print the total execution time
    double elapsed_time = (end.tv_sec - start.tv_sec) * 1000. +       \
                (end.tv_usec - start.tv_usec) / 1000.;
    printf("cpu all cost %f ms \n", elapsed_time);
复制代码

GPU Profile

OpenCL提供了对GPU Kernel Profile的API,分别获取OpenCL任务的各个环节的时间节点,便于开发者进行性能优化。

// opencl init codes 
...
// cl gpu time profile
cl_event timing_event;
cl_ulong t_queued, t_submit, t_start, t_end;
// add event when clEnqueueNDRangeKernel
int status = clEnqueueNDRangeKernel(runtime.queue, runtime.kernel, 1, nullptr, &ARRAY_SIZE,
nullptr, 0, nullptr, &timing_event);
check_status(status, "clEnqueueNDRangeKernel failed");
clWaitForEvents(1, &timing_event);
clGetEventProfilingInfo(timing_event, CL_PROFILING_COMMAND_QUEUED,
sizeof(cl_ulong), &t_queued, nullptr);
clGetEventProfilingInfo(timing_event, CL_PROFILING_COMMAND_SUBMIT,
sizeof(cl_ulong), &t_submit, nullptr);
clGetEventProfilingInfo(timing_event, CL_PROFILING_COMMAND_START,
sizeof(cl_ulong), &t_start, nullptr);
clGetEventProfilingInfo(timing_event, CL_PROFILING_COMMAND_END,
sizeof(cl_ulong), &t_end, nullptr);
printf("t_queued at %llu  \n"
"t_start at %llu  \n"
"t_submit at %llu  \n"
"t_end at %llu  \n"
"kernel execute cost %f ns \n"
"", t_queued, t_start, t_submit, t_end, (t_end - t_start) * 1e-0);
复制代码

通过上述的api可以得到OpenCL Kernel从进去队列,提交、开始、结束的各个时间点,并且可以计算出Kernel运算时长:

t_queued at 683318895157  
t_start at 683318906619  
t_submit at 683318897475  
t_end at 683318907168  
kernel execute cost 549.000000 ns
复制代码
‍丨性能瓶颈
复制代码

识别和定位整个程序的性能瓶颈是非常重要的,没有找到性能的瓶颈,即使其他的环节性能得到优化,也无法使得整个应用性能得到提升。

瓶颈定位

对于OpenCL内核,瓶颈通常是内存瓶颈与计算瓶颈二者之一。
这里提供两个简单的方式,稍微修改代码即可验证:

  • 加入额外的计算逻辑,如何没有影响性能,那应当不是计算瓶颈。

  • 反之,加入更多的数据加载逻辑,如何没有影响性能,那应当不是数据瓶颈。

解决性能瓶颈

成功的定位到性能瓶颈之后,有一系列的手段可以去针对性的解决:

  • 如果是计算瓶颈,可以尝试一些降低计算复杂度的方式、减少计算数的方式,或者使用 OpenCL提供的 fase relax math 或者 native math 等。在精度不高的时候可以使用fp16替代fp32进行计算。

  • 如果是内存瓶颈,可以尝试去优化内存的访问策略,如使用向量化的内存加载和存储,利用本地内存或者纹理内存等。在可能的情况下使用更短的数据类型,可以有效的降低内存带宽。


八、总结

In this article, Qualcomm Adreno GPU is used as an example to further elaborate the design idea of ​​OpenCL, and at the same time, it describes some general methodologies for OpenCL high-performance programming. Due to the limited space, the more detailed content has not been fully expanded, and small partners who are interested in this direction can continue to pay attention to the official account of "Baidu Geek Said".

9. References

[1] OpenCL-Guide

github.com/KhronosGrou…

[2]OpenCL-Examples

github.com/rsnemmen/Op…

[3]Mali-GPU

en.wikipedia.org/wiki/Mali\_…

[4]Adreno-GPU

en.wikipedia.org/wiki/Adreno

Recommended reading:

Introduction to OMAX, a Large-Scale C++ Compilation Performance Optimization System

Evolution of Baidu's Smart Mini Program Inspection and Scheduling Solution

Mobile Heterogeneous Computing Technology - GPU OpenCL Programming (Basic)

Cloud native enables development and testing

Guess you like

Origin juejin.im/post/7104179560429649928