OpenMP 在 Android 多核编程中的研究与运用
王冲;杨斌
【摘 要】As arisen technology in desktop system,OpenMP is very mature in PC platform,but most of Android developments use tradi-tional single-core mode.Google’s NDK R9 provides the support for OpenMP library.This paper describes the application of OpenMP in Android and corrects the existing problems.%作为在桌面系统上兴起的技术,OpenMP 在 PC 平台上已经非常成熟,但是在嵌入式领域,尤其是 Android 的开发大多还停留在传统的单核模式。Google 推出的 NDK R9提供了对 OpenMP 函数库的支持,本文介绍了 OpenMp 在 An-droid 上的运用,并对存在的问题进行了修正。
【期刊名称】《单片机与嵌入式系统应用》
【年(卷),期】2014(000)008
【总页数】3页(P24-26)
【关键词】Android;多核;NDK;OpenMP
【作 者】王冲;杨斌
【作者单位】西南交通大学信息 科学与技术学院,成都 610031;西南交通大学信息 科学与技术学院,成都 610031
【正文语种】中 文
【中图分类】TP311
虽然多核平台具有很大的潜能,但由于多核软件开发工具和标准的缺乏减缓了它们的全面普及。编程人员要想从这些系统中获得更大的好处,可能还需要编写底层代码、调度工作单元,并管理内核之间的同步。作为在桌面系统上兴起的技术,OpenMP在PC平台上已经非常成熟,但是在嵌入式领域,尤其是Android平台的开发,大多还停留在传统的单核模式。虽然Google增加了对OpenMP的支持,但是在使用上还存在一些问题,如无法在用户线程中使用OpenMP,本文对这个问题进行了研究并提出解决方案,最后通过测试程序进行验证。
OpenMP是由OpenMP Architecture Review Board牵头提出的,已被广泛接受,是用于共
享内存并行系统的多线程程序设计的一套指导性注释语句(Compiler Directive)。OpenMP支持的编程语言包括C/C++和Fortran;而支持OpenMP的编译器包括Intel Compiler和Sun Studio,以及开放源码的Open64和GCC编译器。OpenMP提供了对并行算法的高层抽象描述,程序员可以通过在源代码中加入专用的pragma来指明自己的意图,然后编译器自动将程序进行并行化,并在必要的地方加入同步互斥以及通信。当选择忽略这些pragma,或者编译器不支持OpenMP时,程序又可退化为通常的程序(一般为串行),代码仍然可以正常运作,只是不能利用多线程来加速程序执行。
OpenMP提供的这种对于并行描述的高层抽象降低了并行编程的难度和复杂度,这样程序员可以把更多的精力投入到并行算法本身,而非其具体实现细节。对基于数据分集的多线程程序设计,OpenMP是一个很好的选择。同时,使用OpenMP也提供了更强的灵活性,可以较容易地适应不同的并行系统配置。线程粒度和负载平衡等是传统多线程程序设计中的难题,但在OpenMP中,OpenMP库从程序员手中接管了这两方面的部分工作。
OpenMP使用分叉-联接(Fork-Join)模型实现并行执行。程序从单线程或主线程开始,当进入并行区域时,主线程将创建一组并行的工作线程,位于并行模块内的语句由工作线程并行
执行。在并行区域末端,所有线程等待得到同步(联接),如图1所示。在这个阶段完成之后开始串行执行过程。OpenMP的主要特性包括并行循环和分区,以及可能通过动态调度加以执行的任务身份。并行区域中的数据可以被所有线程共享,或对每个线程保持私有属性。因此它能帮助应用程序开发人员减小代码所占的内存空间。
OpenMP中最主要的是编译指导语句,一条编译指导语句由directive(命令,也叫指令)和clause list(子句列表)组成。在C/C++中,OpenMP编译指导语句的使用格式为:
#pragma omp <directive> [clause[[,] clause] ...]
并行计算可分为任务并行和数据并行。任务并行是把多个独立的工作分开同时执行,数据并行则把大的任务化解成若干个相同的子任务,处理起来比任务并行简单。OpenMP的特性使我们很容易实现数据的并行分解,使其独立运行,C语言中的for循环最适合使用数据并行。
JNI(Java Native Interface)是Java本地调用接口,它使得运行于Android平台的Java程序可以使用C、C++甚至汇编语言编写的动态链接库。在需要频繁访问内存或复杂计算的情况下,
使用C动态链接库比在Android平台上使用Java语言实现相同功能更具有效率。NDK(Native Development Kit)提供了一系列的工具,可以生成ARM二进制码的动态库,并且能自动地将生成的动态库和Java应用程序一起打包成Android系统可以直接安装的apk安装包,即NDK可以将包含JNI接口函数的C源程序文件编译生成动态库,供Android应用程序调用,提高了对现有代码的重用性,而加快了开发进度。由于OpenMP不支持Java语言,所以使用OpenMP就需要用到Android NDK。NDK允许开发者通过JNI将开发C(或C++)的动态库嵌入到Java程序中,并能自动将so和Java应用一起打包成apk,JNI构成了Java和C/C++互相沟通的桥梁。OpenMP使用示意图如图2所示。
2.1 引用OpenMP函数库
由于最新的NDK已经添加了对OpenMP函数库的支持,所以只需要在Android.mk文件中添加OpenMP的标志,然后使用编译指导语句来并行化代码。
LOCAL_CFLAGS += -fopenmp
LOCAL_LDFLAGS += -fopenmp
#include <omp.h>
#pragma omp parallel for
for(int i = 0;i < length;i++) {
/* */
}
编译器将根据可用的CPU核数目设置线程数,自动对C/C++代码并行化。
2.2 存在的问题
经过跟踪测试,当前的NDK版本仅支持在主线程中使用OpenMP(在用户线程中使用OpenMp将会导致程序崩溃),但是如果仅在主线程中处理数据,无法达到UI与数据分离的目的,在处理一些耗时操作时将大大影响用户体验,也失去了并行的意义。GOMP线程创建流程图如图3所示。
通过对源代码分析,发现是由于libgomp/libgomp.h中的gomp_thread函数返回NULL。
进一步分析,在GOMP(GCC标准下的OpenMP)中,若使用了TLS(线程局部存储),则设置HAVE_TLS标志,并会产生一个全局变量gomp_tls_data跟踪每个线程的状态,否则将通过pthread_setspecific函数来管理线程特有的数据。由于Android中用户线程不支持TLS,所以只能通过pthread_setspecific函数来管理线程特有的数据,当GOMP创建线程时,libgomp/team.c中的gomp_thread_start函数将设置线程特有的数据,创建独立线程(用户线程)时,线程特有的数据没有设置,从而gomp_thread函数返回NULL,导致程序崩溃。因此,必须在调用gomp_thread时初始化线程特有数据。
2.3 解决方案
由于修改了源代码,所以需要对NDK的交叉编译工具链进行重新编译,方法如下:
① 下载 Android NDK源码。
#git clone tools.android/overview/ndk-git ndk
#cd ndk
# ./build/tools/download-toolchain-sources.sh src
② 修改libgomp.h源代码。
static inline struct gomp_thread *gomp_thread (void){
return pthread_getspecific (gomp_tls_key);
struct gomp_thread *thr = pthread_getspecific (gomp_tls_key);
if(thr == NULL){
thr = create_non_tls_thread_data ();
}
return thr;
}
③ 编译。
嵌入式多线程编程
# ./build/tools/build-gcc.sh --verbose $(pwd)/src $(pwd) arm-linux-androideabi-4.8
④ 将生成的libgomp.a文件拷贝到NDK的安装目录替换。
完成了对NDK的修改,将对OpenMP在Android平台上的性能进行测试。本次测试分别使用单核、双核、4核 的Android设备对800×600的灰度图像进行3×3的均值滤波。
3.1 均值滤波任务并行化
均值滤波是典型的线性滤波算法,它是指在图像上对目标像素f(x,y)给定一个模板,该模板包括了其周围的临近像素(以目标像素为中心的周围m个像素,构成一个滤波模板,即去掉目标像素本身)。再用模板中的全体像素的平均值g(x,y)来代替原来像素值。
g(x,y)=1/m∑f(x,y)
传统的处理方法将会遍历每个像素点,依次处理,而OpenMP的for语句可以将这部分工作并行化。并行for语句语法:
#pragma omp [parallel] for [clauses]
主要实现代码如下:
JNIEXPORT jbyteArray JNICALL Java_com_example_testndk_JniClient_Smooth
(JNIEnv *env, jclass arg, jbyteArray mydata, jint smmode){

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。