zoukankan      html  css  js  c++  java
  • iOS 开发者应该知道的 ARM 结构

    http://news.cnblogs.com/n/68903/

    我在写「NEON on iPhone 入门」的时候,曾以为读者已经比较了解 iOS设备的处理器知识。然而,看过网上的一些讨论,我才发现,原来这些知识并不普及,我的错。此外,我觉得了解这些东西对 iPhone编程有益(不仅仅针对喜欢 NEON 的人),即便你用的是 Objective-C,虽然,不了解也无碍工作,但这些知识会让你成为一个更好的iPhone 程序员。

      基础

      到目前为止,所有的 iOS 设备都使用 ARM 结构处理器,它和台式机上的 x86 和 PowerPC 有些不同,然而绝对不是「特殊」或「小众」的产品。几乎所有的手机(不只是智能手机)都基于ARM,例如几乎所有的 iPod,几乎所有的 MP3 播放器,PDA 和 Pocket PC 更不用说了。任天堂从 GBA 开始转入ARM,它甚至还侵入图形计算器的地盘,出现在一些德仪和惠普的计算器中。如果你还想继续溯本逐源,那么牛顿用的也是 ARM(苹果是 ARM的早期投资者)。而且上面只说了一些小玩意,还有无数的 ARM 处理器运行在嵌入式系统中。

      ARM 处理器因为低功耗和小尺寸而闻名,它的性能在同等功耗的产品中也很出色。这种结构(至少在 iOS平台)使用小端(Little-endian)排序,就像 x86。它和 MIPS、PowerPC 一样,属于 32 位 RISC结构。请注意,模拟器并不运行 ARM 代码,软件会被编译成 x86 可以运行的指令。因此接下来的内容适用于目标设备,而非模拟器。

      ARMv7,ARM11,Cortex A8 和 A4,天哪!

      多年来,ARM 结构演化出几个不同的版本,每一版都增加了新指令,在提升的同时保持了后向兼容的能力。初代 iPhone 使用了 ARMv6结构的处理器(ARM 第六版的简称),而最新的 iPhone 4 支持ARMv7。所以,编译代码的时候,依目标版本的指令集不同,生成不同的指令。汇编程序也一样,代码中使用的指令必须兼容特定的版本。最后,生成机器码,对应 ARMv6 或 ARMv7(或者 ARMv5 和 v4,不过 ARMv6 是 iOS开发的底线,所以这两者就不用考虑了)。目标文件和可执行文件有标注自己对应的版本,可以通过运行 otool -vh foo.o 来查看。

      不过呢,「初代 iPhone 4 搭载了 ARMv6 处理器」这种说法是错误的,因为 ARMv6不是指特定的处理器,而是处理器可以运行的指令集。初代 iPhone 使用了 ARM 11 核心(确切说是ARM1176JZF-S,不过这不重要,只要记得它是 ARM 11 家族的成员就行了),正如刚才提到的,这款处理器采用 ARMv6指令集。之后的 iOS 设备仍采用 ARM11,直到 iPhone 3GS 发布,苹果开始尽数转向 Cortex A8处理器核心(尽管尚不确定,但 iPhone 4 很可能用的就是 A8 )。这个核心采用了 ARMv7 指令集,或这么说,它支持 ARMv7。

      我已经说过,不要在程序里植入设备判断代码,然后通过已知信息侦测设备所支持的 ARM结构。这种代码极不可靠,而且运行在(软件完成后才发布的)新设备上会导致中断。所以请别这么做,否则我发誓,我会跑到你家里废了你。以上知识是为了让你粗略了解,有些设备支持 ARMv7,有些设备支持 ARMv6。至于如何侦测,我马上会谈到。

      不过,你可能会想「iPad 和 iPhone 4 用的是 A4,不是 Cortex A8 吧?」不然,A4其实是一个完整的单片系统(SOC),其中不只有 Cortex A8内核,还包括了图形硬件、音视频编码加速器和其他数字模块。单片系统和处理器是两个很不相同的概念,处理器在硅片上甚至不占主要空间。

      如果不懂得如何利用,即使设备支持 ARMv7也无济于事。当然应用新的指令集也没有问题,但如果总是这么做,早先的设备就无法运行你写的代码了,我猜,这也许不是你想要的结果。那么,应该如何侦测设备所支持的结构呢?— 只有确定它是否支持 ARMv7 才能好好利用啊。答案是:没必要知道。相反,把代码编译两次,一次针对ARMv6,另一次针对ARMv7,接着把这两个可执行文件打包成一坨肥硕无比的二进制文件。好了,运行的时候,设备会自己决定打开哪一个更好。是的,Mach-O不仅可以用来组合完全不同的 CPU 结构(例如 PowerPC 和 Intel),或者相同结构的 32 位和 64位版本,它还可以对付同一种结构的 2 个变体,用 Mach-O 的术语来说,这叫 CPU子类。从程序员的角度看,这么做的结果是:编译时决定一切。针对 ARMv6 编译的代码只运行在 ARMv6 设备上,同理,针对 ARMv7编译的代码只运行在 ARMv7(或者更好)的设备上。

      如果你读过了我写的 NEON那帖,你也许会记得我推荐过一种在运行时(Runtime)中侦测和选择结构的方法。如果再去看,你会发现我已经把那部分移走了,现在,我不建议那么做,因为虽然这的确有用,但不能确保(或者说,所需技巧太复杂而不能确保不出错)在将来的 ARMv8 处理器上能够稳定运行。文档中是否有相关 API的状态不重要(不在 iOS 的手册页中),如果你想在 ARMv6 上运行又希望利用 ARM7v,就用我刚才讲过的办法。

      补充一点:在 iOS 环境下,ARM 结构不一定能反映处理器的型号。例如,对应 ARMv6 的 iOS代码需要浮点指令的支持(VFPv2,准确的说),对 ARMv6 而言,虽然这是可选项,不过自从第一代 iPhone发布以来就已经存在。所以,如果在 iOS 开发(例如编译器 -arch 设置或一个可执行文件的 CPU 子类)中提到了ARMv6,就表示需要硬件浮点的支持。这对 ARMv7 和 NEON 也一样:虽然 NEON 实际上是 ARMv7-A配置的一个可选项,但是因为它出现在所有支持 ARMv7 的 iOS 设备中,所以,提到 iOS NEON 即部分提到 ARMv7。

      条件执行

      ARM 结构一个实用的功能是,大多数指令可以有条件地执行 — 如果条件不满足,则指令无效。这可以缩短过程,让区块(Blocks)部署地更为有效。通常的办法是,如果区块不符合条件则跳过,但是通过把判断指令植入块内,省去了该步骤。

      如果这仅仅是编译器用来提高代码效率的手段,我就不会在这里提到它了。虽然,这的确是它的一个功用,但之所以提到是因为,在调试(Debugging)时,它可能会令人吃惊。事实上,有时你会发现,调试器会进入状态为假的条件区块(ifblock,例如早期的错误回报),或者进入 if-else的两个分支。这是因为,虽然代码尽数经过处理器,但是一部分没有实际执行,即条件执行。另外,如果你把断点置入这样的条件区块中,即使状态为假,它仍有可能执行。

      话虽如此,但是在我有限的测试中,编译器似乎拒绝在调试配置中生成条件执行指令。因此它应该只发生在调试优化后的代码的时候,不幸的是,有时候你没得选择,只能这么做。

      Thumb

      Thumb 指令集是 ARM 指令集的一个子集,经过压缩,因此指令只有 16bits(所有 ARM 指令的大小都是32bits,它仍然是 32 位结构,只是占用的空间少了)这不是一个全然不同的结构,而应将其视作常见 ARM指令和功能的缩写。它的优点,显然是大为缩小代码尺寸,节约内存和缓存,以及代码带宽。虽然更适用于内存紧张的微控制器型应用程序,但是在 iOS设备中,它仍然有用处,也因为如此,Xcode 默认在 iOS 项目中打开这项功能。虽然代码尺寸因此减少很多,但是不可能达到50%,因为有时候完成一个 ARM 指令需要对应的两个 Thumb 指令。ARM 和 Thumb指令不能随意混合,处理器需要针对二者切换不同的模式,而这只能在调用或从函数返回时发生。

      当目标平台是 ARMv6 的时候,编译 Thumb 指令面临着很大的权衡取舍。ARMv6 的 Thumb代码可以访问的寄存器较少,缺乏条件指令,特别是,它不能使用浮点硬件,例如浮点加法、减法、乘法等等。使用浮点 Thumb代码必须调用系统函数,没错,听起来就像速度很慢的感觉。基于这个原因,针对 ARMv6 时,我建议禁用 Thumb模式,但倘若你执意如此,请确保先分析代码。如果某些部分速度很慢,至少先试着禁用那部分 Thumb(很容易,在 Xcode 中使用命令行参数,-mno-thumb)。请记住,浮点运算在 iOS 中非常普遍,因为 Quartz 和 Core Animation 使用浮点坐标系统。

      当目标变成了 ARMv7 的时候,所有这些缺点就消失了:ARMv7 包含 Thumb-2,它是 Thumb指令的扩展集,增加了条件执行和可以访问所有 ARM 寄存器以及硬件浮点与 NEON 的 32 位 Thumb 指令。用 Thumb-2缩减代码的代价几乎没有,所以最好是开着(如果关掉了请重新打开)。在 Xcode 的条件生成选项中,对 ARMv7 打开,对 ARMv6 关闭。

      你也许在网上听到人们说,代码需要「互通」(Interworking)才能使用 Thumb,除非你想写汇编代码,否则不必担心,因为 iOS平台的所有代码都是互通的。当显示汇编的时候,Shark 可能难以判断函数是 ARM 还是Thumb。如果你看到无效或无意义的指令,最好互相对调一下。

      对齐

      iOS 支持非对齐访问,然而比起对齐访问,它的速度更慢,建议不要使用。在某些特殊情况下(涉及加载/存储多个指令,如果你有兴趣的话),非对齐访问的速度可能比对齐访问慢上百倍,因为处理器无法处理,而且必须请求操作系统的协助(参考此文,这和 PowerPC 上导致非对齐双精度浮点数变得超慢是同一个现象)。所以,要小心,而且,对齐仍然重要。

      除法

      这家伙总让每一个人吃惊。打开 ARM 结构手册(如果你还没有,请看「NEON on iPhone 入门」的结构概览那节),找到整数除法指令。去吧,我等你。找不到?正常正常,根本没有的。是的,ARM 结构不支持硬件整数除法,必须通过软件执行。如果你编译下面的代码:

    int ThousandDividedBy(int divisor)
    {
    return 1000/divisor;
    }

      在汇编代码中,你会看到编译器插入了一个调用函数的「___divsi3」— 这是一个系统函数,用来执行软件除法(注意,除数不能恒定,否则除法可能会被转换为乘法)。这意味着,在 ARM 上,整数除法实际代表了操作系统的性能。

      「不过,」看完手册归来,你也许会说:「你错啦!里面有 ARM 除法指令,甚至还有两个呢!在这里,sdiv 和udiv!」不好意思给您颇凉水啦,这些指令只可用于 ARMv7-R 和 ARMv7-M 配置(分别指实时和嵌入式环境 —例如马达的微控制器和手表),iOS 设备用的 ARMv7-A 不支持,很抱歉!

      GCC

      GCC 生成的 ARM 代码质量之糟已不是秘密。在其他一些基于 ARM 的平台上,专业开发者使用 ARM 自家提供的工具链 — RVDS。不过,RVDS 不支持 OSX 用的 Mach-O 运行时,只支持 ELF 运行时,所以在 iOS 平台上没辙。但至少还有 GCC的替代品,比如现在可以用 LLVM。虽然我没怎么测试,但是当使用 LLVM 的时候,至少看到了 64 位整数码的显著改进(这一点,GCC 在 ARM 上尤其弱)。假以时日,LLVM 全面超越 GCC 可以指望。

      你瞧,现在你是更好的 iOS 开发者了!

  • 相关阅读:
    CF1051F The Shortest Statement 题解
    CF819B Mister B and PR Shifts 题解
    HDU3686 Traffic Real Time Query System 题解
    HDU 5969 最大的位或 题解
    P3295 萌萌哒 题解
    BZOJ1854 连续攻击游戏 题解
    使用Python编写的对拍程序
    CF796C Bank Hacking 题解
    BZOJ2200 道路与航线 题解
    USACO07NOV Cow Relays G 题解
  • 原文地址:https://www.cnblogs.com/qq378829867/p/4137503.html
Copyright © 2011-2022 走看看