精通安卓性能优化-第三章(一)

第三章 NDK进阶

第二章告诉你如何使用Android NDK建立一个工程和怎样在Android应用中使用C/C++代码。在许多情况下,这就够了。然而,当需要更加优化的时候需要挖的更深一些。

在本章,你将亲自动手,学习如何使用一个低级语言去利用CPU提供的所有的优势,而不是仅仅使用普通的C/C++。本章的第一部分演示了使用汇编优化函数的几个例子和ARM指令的概述。第二部分GCC编译器支持的C扩展,可以用来提升应用的性能。最终,给出了使代码相对快一些的几个简单结论。

最新的Android NDK支持armeabi、armeabi-v7a和x86,本章将聚焦于前两个,因为Android大多数在基于ARM的设备上开发。如果你计划写汇编代码,ARM是你的首选。尽管第一台Google TV设备是基于Intel的,Google TV不支持NDK。

汇编

NDK允许在Android应用中使用C/C++。第二章展示了在C/C++代码编译后的native代码看起来是什么样的,你怎么使用objdump –d去反汇编一个文件(object文件或者库)。比如,computeIterativelyFaster的ARM汇编代码在Listing 3-1给出。

Listing 3-1 computeIterativelyFaster的C实现的ARM汇编代码



除了允许在应用中使用C/C++,NDK同样允许你直接写汇编代码。直接的讲,这个特性不是NDK独有的,因为汇编代码被GCC编译器支持。Android NDK使用了GCC编译器。结果,在本章学习的几乎所有的东西可以同样应用到其他的工程,比如iOS应用。

就像你看到的,汇编代码非常难以阅读,更不要说写了。然而,能够理解汇编代码可以让你容易的识别瓶颈,因此可以更容易的去优化你的应用。它也可以给你吹牛的权利。

为了熟悉汇编,我们看3个简单的示例:
(1) 计算最大公约数
(2) 转换颜色格式
(3) 8位数平均值的并行计算

这些例子对刚刚接触汇编的开发者来说是足够简单的,而且他们展现了汇编优化的重要原则。因为这些例子仅告诉你可用指令的一部分,在后面有完整的ARM指令集介绍以及uberpowerful ARM SIMD指令的主要介绍。最终,你将学习如何动态的检测CPU功能是否可用,这是一个强制的步骤,因为在你应用中这些功能不是在所有的设备上面都是可用的。

最大公约数

两个非零整数的最大公约数(Greatest common divisor)是可以被这两个数整除的最大的正整数。比如,10和55的最大公约数是5。在Listing 3-2中给出了两个整数的最大公约数计算的一个实现。

Listing 3-2 最大公约数的简单实现

unsigned int gcd (unsigned int a, unsigned int b)
{
    // a、b的绝对值必须不同(否则,无限循环)

    while (a != b)
    {
        if (a > b) 
        {
            a -= b;
        } else {
            b -= a;
        }
    }

    return a;
}

如果在Application.mk中定义了APP_ABI,比如你的应用支持x86、armeabi、armeabi-v7a,将会有三个不同的库。反编译3个库将得到不同的汇编代码。然而,因为armeabi和areambi-v7a你可以选择编译成ARM或者Thumb模式,实际上总共有5段代码要review。

TIP:从NDK r7开始可以定义APP_ABI为all(APP_ABI := all),而不是分别指定每个ABI。当一个新的ABI被NDK支持的时候,只需要执行ndk-build就可以了,不需要修改Application.mk。

Listing 3-3给出了x86的汇编代码,Listing 3-4给出了ARMv5的汇编代码,Listing 3-5给出了ARMv7的汇编代码。因为不同版本的编译器会产生不同的代码,你得到的代码可能会和这里稍有不同。产生的代码也会依赖于优化级别和定义的其他选项。

Listing 3-3 x86汇编代码



如果你熟悉x86助记符,可以看出这段代码大量使用了jump指令(jne, jmp, je, jb)。同样的,大多数指令是16位的(比如, “f3 c3”),有些是32位的。

NOTE: 保证使用正确版本的objdump去反编译object files和libraries。比如,使用arm的objdump去反编译x86的object文件将会得到如下的信息:
arm-linux-androideabi-objdump: Can’t disassemble for architecture UNKNOWN!

Listing 3-4 ARMv5汇编代码(ARM模式)


Listing 3-5 ARMv7a汇编代码(ARM模式)


事实证明,在ARM模式下GCC编译器为armeabi和armeabi-v7a产生了相同的代码,如Listing 3-2所示。这不总是正确的,因为编译器经常利用在更新的ABI定义的新指令。

因为你可以决定在Thumb模式下编译代码而不是arm模式,我们来看在Thumb模式下产生的代码。Listing 3-6给出了ARMv5的汇编代码(Application.mk的armeabi ABI),Listing 3-7给出了ARMv7汇编代码(Application.mk的armeabi-v7a ABI)。

Listing 3-6 ARMv5汇编代码(Thumb模式)


Listing 3-6中所有的指令是16位的(即”e7f4”, 列表中的最后一条指令),因此12条指令需要24字节的空间。

Listing 3-7 ARMv7汇编代码(Thumb模式)


这一次,两个listing是不同的。ARMV5使用Thumb指令集(所有指令都是16位),ARMV7架构支持Thumb2指令集,指令可以是16位的或者32位的。
事实上,Listing 3-7很像Listing 3-5。主要的不同是Listing 3-7的ite(if-then-else)指令,ARM代码占40字节,而Thumb2代码占28字节。

NOTE: 尽管ARM架构是有优势的,阅读x86汇编代码没有什么坏处。

通常情况下,GCC编译器做的很好,不需要担心产生的代码。然而,如果你写的一段C/C++代码被证实是应用的一个瓶颈,就需要认真的阅读编译器产生的汇编代码,决定是否你需要自己去写汇编代码。编译器通常产生高质量的代码,你不会做到更好。也就是说,在某些情况下,装备指令集知识既有熟悉和轻微的痛苦,你可以达到比编译器更好的效果。

NOTE:考虑修改C/C++代码去达到更好的性能通常是比写汇编代码更加简单。

gcd函数实际上可以不同的实现,代码不仅可以更快也更加紧凑。如Listing 3-8所示。

Listing 3-8 手写汇编代码


不包括用来从函数返回的最后一条指令,核心的算法仅用4条指令实现。测量结果表明这种实现方式比较快。注意Listing 3-8中的一条CMP指令的调用,和Listing 3-7中的两条进行比较。

这段代码可以复制到一个文件,命名为gcd_asm.S,并且添加到Android.mk的编译列表里面。因为这个文件使用ARM指令,显然它不可以x86 ABI为目标。结果,Android.mk需要保证这个文件是编译列表的一部分,当它和ABI兼容的情况下。Listing 3-9演示了如何去修改Android.mk。

Listing 3-9 Android.mk


因为gcd_asm.S已经使用汇编代码写了,结果object文件会和源文件非常相似。Listing 3-10给出了实际的反汇编代码,反汇编代码几乎和源代码相同。

Listing 3-10 gcd_asm的反汇编代码


NOTE: 汇编在有些情况下可能会替换一些指令,所以你可能仍然观察到你写的代码和反汇编的代码的一点差别。

通过简化汇编代码,我们得到了更好的结果而没有使维护更加复杂。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值