[案例研究:将 Stream 应用移植到 Android*]

目标

本文展示了如何通过创建使用原生共享库的 Android* 应用将 Stream 性能指标基准评测应用移植到 x86 平台。关于最新的应用详情,请访问:http://www.streambench.org

本文描述了一个更先进的使用案例,利用原生开发套件(NDK)将应用移植至 Android*。具体而言,要移植的应用为 Stream 性能指标基准评测应用。Stream 自发布起至今已经有很长一段时间,它设定了展示“真实”内存带宽的评测标准(与“理论带宽”标准相对应,理论带宽相对典型实际结果而言学术性更强)。

Stream 为开发人员提供了一个多线程化选项。如果只是对应用进行单线程处理(即“调优”),一个简单的 NDK 编译操作即可准确无误地实现这一点。 然而,在默认情况下,应用将采用 OpenMP* 作为实施多线程化的方法。截至 2011 年 10 月,Android* 在链接时并不支持 OpenMP*。开发人员必须移植应用,以使用 POSIX* 线程。然后,使用 NDK 编译应用,让其作为原生库运行,为使用 POSIX* 线程(pthreads)提供相应的基础设施。

先决条件

本文假设 Android* 软件开发套件(SDK)经过适当设置,可与 Eclipse* IDE 共同使用。此外,本文还假设已安装并配置了最新的原生开发套件(NDK)。开发人员使用 NDK 将原生 C/C++ 代码编译为一个原生库,供包装程序 Android* 应用使用。本文将为您展示面向 Stream 的 NDK 编译和链接。

请参阅有关如何获取和安装 Android* SDK 与 NDK 的其它 ISN 文档。

移植步骤:Android.mk 文件

为 Stream 创建一个新工程文件夹。该项目将与 Android* NDK(此处采用 NDK r6b)共同使用。在新工程文件夹中创建一个“jni”文件夹,并在其中保存(或从头创建)一份模板 Android.mk 文件。主要变更采用加粗字体显示:

...
LOCAL_MODULE:= libstream
...
LOCAL_SRC_FILES:= \
stream.c 


根据惯例,LOCAL_MODULE 的名称以“lib”开始,且假定“stream.c”是主要的应用源文件。

现在在 NDK 构建中为应用启用 openMP*。这要求必须同时在编译时和链接时启用。需在 Android.mk 中进行以下更改:

LOCAL_LDLIBS := -ldl -llog -lgomp
LOCAL_CFLAGS := -fopenmp 


尝试使用该命令构建项目:

ndk-build APP_ABI=x86

您将发现,以上操作会导致构建错误,这是因为 NDK 不理解 -lgomp 标记。目前,OpenMP* 链接并不支持 Android*。. 幸运的是,我们可以移植应用,转为使用 POSIX* 线程(pthreads)。

构建时和链接时标记需要进行如下更改:



图 3.1:POSIX* 线程标记

移植步骤:重写 Stream 以使用 POSIX* 线程

注:本文并不会全面介绍有关支持 pthread 的代码的详细信息,只是进行了简要概述,重点论述了 NDK 相关问题。

作为可靠的起点,与放弃使用 OpenMP* 基础设施相比,在 Stream 中简单地添加一个面向 pthread 支持的 make 是更明智的选择。在这里,假定 make 标记的名称为 _PTHREADS。然后,不论何时,只要面向 OPENMP* 的代码块以#pragma omp parallel { … } 形式出现,pthread 实施中的语义等价形式将呈现为:

    //<NDK porting>
    //#pragma omp parallel for
    for (j=0; j<THREAD_OFFSET; j++)
    {   
        a[j + (THREAD_OFFSET * thread_ID)] = 1.0; 
        b[j+ (THREAD_OFFSET * thread_ID)] = 2.0;
        c[j+ (THREAD_OFFSET * thread_ID)] = 0.0;
     }

图 4.1:POSIX* 中的并行循环

在这种情况下,THREAD_OFFSET 被定义为 N / MAX_THREADS,其中 N 代表 Stream 源代码中的阵列问题大小。因此,THREAD_OFFSET 的作用是允许多条线程在阵列的不同区域并发运行。thread_ID 单纯地代表进入该代码的线程的 ID,线程创建流程结束后,这些 ID 将存储于一个阵列中。

在某些情况下,还可使用互斥锁和解锁。借助互斥锁和解锁,我为线程构建了我自己的同步屏障,因为从语义角度分析,我希望所有线程在并行代码部分之后全部实现同步,借以模拟 openMP* 实施的并行部分。注:考虑到线程的所有细微时间差别,屏障的实施绝非易事。事实上,开发人员常常需要执行该实施,因为屏障是维持 POSIX* 兼容性的一种选择。

最终,一条线程被指定为“主”线程。该线程负责创建 pthreads,处理任何非并行计算,以及解析 / 展示最终性能指标基准评测结果。

满足以下条件后,开发人员便可开始进入下一阶段:
- POSIX* 线程实现已完成
- 开发人员验证了上面实现
- 上述 NDK 编译过程成功完成

完成编译原生 Stream 代码后

现在,可以开始使用从包装程序(基于 Java*)Android* 软件包中调用原生(已编译)代码的典型流程了。在这种情况下,使用 JNI 就如同基本的“Hello World”示例一样简单。

只是对 Stream 的输入方法进行了修改,让它具备典型的 JNI 签名,如下所示:



图 5.1:修改后的 Stream 输入方法

该标题假设在 Eclipse* Android* 项目中添加了“NativeCaler.java”,其中源文件被用来使用 System.Load() 命令调用 Stream 应用。当然,开发人员可据此选择不同的命名法。另外请注意,在这个简单的示例中,并没有使用任何方法参数,但开发人员可以基于应用进行选择。

总结

本文高度概括了如何确保 Stream 应用在作为 Android* 软件包的一部分使用时能够适当地进行构建和链接并支持多线程,此外还讨论了使用 Android* NDK 进行构建和链接时,移植代码以使用 POSIX* 线程而非 openMP* 的流程。



* 文中涉及的其他名称及商标属于各自所有者资产

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值