Monday, July 19, 2010

iOS開発者の為のARM講座

Wandering Coder: A few things iOS developers ought to know about the ARM architecture
by Pierre Lebeaupin

以前iPhonenのNEONについて書いたとき,
iOSデバイスのプロセッサについても読者に知って頂きたいと思っていた.
しかし,オンラインでのディスカッションでこの知識は一般的ではないことが分かった.
が,この知識はiPhoneプログラミングにおいて重要だと感じた.
(もしNEONnい興味が無い場合だが)
これはObject-cでハイレベルプログラミングを行っている場合でもだ.
知らなくても生きていけるだろう.
しかし知る事で,iPhoneプログラマとして成長できるのだ.

When I wrote my Introduction to NEON on iPhone, I considered some knowledge about the iOS devices’ processors as assumed to be known by the reader. However, from some discussions online I have realized some of this knowledge was not universal; my bad. Furthermore these are things I think are useful for iPhone programming in general (not just if you’re interested in NEON), even if you program in high-level Objective-C. You could live without them, but knowing them will make you a better iPhone programmer.

基本
現在発売している全てのiOSデバイスは全てARMアーキテクチャだ.
そして,このアーキテクチャがデスクトップのx86やPowerPCと少し違う事に気がつくだろう.
しかし,特別,あるいはニッチなアーキテクチャではない.
身の回りにある携帯電話(スマートフォンだけではない)はARMベースだ.
実際,全てのiPodはARMベースで,
ほとんど全てのMP3プレーヤがそうだ.
PDAやPocket PCもだいたいARMだ.
任天堂のポータブルゲーム機はゲームボーイ時代からARMで,
現在はグラフィック演算をTIとHPモデルでは組み込んでいる.
もし,血統書がほしいのであれば,NewtonはARMベースだ.
(実際,Appleは早期からARMに投資していたのだ)
これがガジェットの現実だ.
数えきれない程のARMプロセッサが組み込み用途で出荷されているのだ.

The Basics

All iOS devices released so far are powered by processors based on the ARM architecture; as you’ll see, this architecture is a bit unlike what you may be used to on the desktop with x86 or even PowerPC. However, it is not a “special” or “niche” architecture: nearly all mobile phones (not just smartphones) are based on ARM; practically all iPods were based on ARM, as well as nearly all mp3 players; PDAs and Pocket PCs were generally ARM-based; Nintendo portable consoles are based on ARM since the GBA; it is now invading graphic calculators with some TI and HP models using it; and if you want pedigree, know the Newton was based on ARM as well (in fact, Apple was an early investor in ARM). And that’s only mentioning gadgets; countless ARM processors have shipped in unassuming embedded roles.

ARMプロセッサは小さなシリコンダイであることが有名で,
省電力で,パフォーマンスももちろん高い.
ARMは(少なくともiOSプラットフォームで使われている)リトルエンディアンで,x86と同じだ.
また,32bit RISCアーキテクチャで,MIPS, PowerPCなどと同じだ.
あと,(iPhone)シミュレータはARMコードを実行してはいない.
シミュレータ用にビルドした時はx86ネイティブにコンパイルされる.
だからアプリはターゲットでバイス上でも動かす必要があるのだ.

ARM processors are renowned for their small size on the silicon die, low power usage, and of course their performance (within their power class). The ARM architecture (at least as used in the iOS platform) is little-endian, just like x86. It is a 32-bit RISC architecture, like MIPS, PowerPC, etc. Notice the simulator does not execute ARM code, when building for the simulator your app is compiled for x86 and executes natively, so none of the following applies when running on the simulator, you need to be running on target.

ARMv7, ARM11, Cortex A8, Cortex A4
ARMアーキテクチャは少し異なるバージョンが存在する.
どれも新しい機能や特徴を追加し,過去バージョンとは互換性がある.
最初のiPhoneはARMv6が搭載されていた.
最近の物はARMv7だ.
なのでコードをコンパイルするとき,
ターゲットとしているデバイスのアーキテクチャバージョンを指定する.
するとコンパイラはそのバージョンの機能に合わせてコンパイルするのだ.
アセンブラも同様で,そのバージョンの命令セットに合わせる.
最後に,ARMv6やARMv7にあったオブジェクトコードが作成される.
(ARMv5やARMv4もあるが,iOS開発ではターゲットにならない)
このオブジェクトと実行可能ファイルは,ターゲットに合わせたマークがされている.
otool -vh foo.o
とコマンドしてみるといい.

ARMv7, ARM11, Cortex A8 and A4, oh my!

The ARM architecture comes in a few different versions developed over time; each one added some new instructions and other improvements, while being backwards compatible with the previous versions. The first iPhone had a processor that implements ARMv6 (short for ARM version 6), while the latest devices have processors that can support ARMv7. So when you compile code, you specify the architecture version you’re targeting, and the compiler will restrict the instructions it generates to those available in that architecture version; the same goes for the assembler, which will check that the instructions used in the code are present in the specified architecture version. In the end, you have object code that targets a specific architecture variant, ARMv6 or ARMv7 (or ARMv5 or ARMv4, but given that ARMv6 is the baseline in iOS development, you’re very unlikely to target these); the object and executable files are in fact marked with the architecture they target, run otool -vh foo.o on one of your object or executable files sometime.

しかし,オリジナルiPhoneはARMv6である,と言っても意味が無い.
ARMv6とは,特定のプロセッサを指しているわけではなく,
どの命令セットが動くかしか指していないのだ.
初代iPhoneのプロセッサコアはARM11で,
(正確にはARM1176JZF-Sと言うが,大した違いではない,ARM11の一員と覚えておけばよい)
前述した通り,このプロセッサはARMv6を実現している.
次のデバイス(iPhone 3Gのこと)は同じようにARM11を使っており,
iPhone 3GS移行ではCortex A8プロセッサを使用している.
(この執筆時点の話.iPhone4についてはよくわからない)
Cortex A8はARMx7を実現している.

However, it does not make sense to say that the original iPhone had “the ARMv6 processor”: ARMv6 does not designate a particular processor, but the set of instructions a processor can run. The processor core implementation used in the original iPhone was the ARM11 (it was the ARM1176JZF-S, to be really accurate, but it matters very little, just remember it was a member of the ARM11 family); as mentioned earlier, this processor implements ARMv6. Subsequent devices used ARM11 as well, up until the iPhone 3GS which started using the Cortex A8 processor core, used in all iOS devices released since then at the time of this writing (this is not yet certain, but strongly suspected, in the case of the iPhone 4). This core implements the ARMv7 instruction set, or in short, supports ARMv7.

言ってはいなかったが,
決して,どのデバイスであなたのコードが動いているかを特定するコードや,
どのアーキテクチャがそのデバイスでサポートされているかを特定するコードを書いてはいけない.
このような信頼性の無いコードをかくと,
あなたのアプリケーションがデバイスを壊す可能性がある.
だから,止めてほしい.
どうしてもやるなら,僕が家に行って止めるまでだ.
この情報は単にARMx7をサポートしているか,ARMx6では動かないかだけだ.
すぐにわかるだろう.

Now having said that, DO NOT go around and write code that detects which device your code is executing on and tries to figure out which architecture it supports using the known information on currently released devices. Besides being the most unreliable code you could write, this kind of code will break when run on a device released after your application. So please don’t do it, otherwise I swear I will come to your house and maim you. This information is just so that you have a rough idea of the installed base of devices that can support ARMv7 and the ones that can only run ARMv6; I’ll get to detection in a minute.

「iPadとiPhone4はApple A4プロセッサで,Cortex A8ではないが?」
と思ったかもしれない.
A4は実は統合チップで,Cortex A8コアとグラフィックハードウェアを搭載している.
ビデオやオーディオのコーデックアクセレータや他のデジタルファイル用だ.
Soc(System on a chip)とプロセッサコアは全く違う物だ.
プロセッサコアはそんなにシリコンダイの大部分を占める物ではない.

But you may be wondering: “I thought the iPad and iPhone 4 had an A4 processor, not a Cortex A8?!” The A4 is in fact the whole applications System on a Chip, which includes not only a Cortex A8 core, but also graphics hardware, as well as video and audio codec accelerators and other digital blocks. The SoC and the processor core on it are very different things; the processor core does not even take the majority of the space on the silicon die.

最新デバイスに乗っているARMv7の新機能はほとんど意味のないものだ.
もしその機能の意味が分からなかったり.使おうとしない限りは.
しかし常に新機能を追い続けると,コードは初期のデバイスで動かなくなる.
これは不本意だろう.
では,どうやってデバイスでどのアーキテクチャバージョンがサポートされているかを検知し,
ARMv7の機能を使う事ができるのだろう?
これは,やるべきではない.
代わりに,コードをARMv6とARMv7にコンパイルするのだ.
二つの実行可能ファイルはファットバイナリとして一緒にできる.
これでデバイスが,どちらのコードを実行するか選択できるのだ.
そう,Mach-Oファットバイナリは別のCPUアーキテクチャ同士のものだけではないのだ
(例えばPowerPCとIntelをユニバーサルバイナリとしたように)
あるいは32bitと64bit同士だけでもない.
同じアーキテクチャの2つの異なる種類(CPUサブタイプとMach-0では呼んでいる)でも可能なのだ.
結論は,プログラマの視点だ.
全てがコンパイルの時に決まる.
ARMv6用にコンパイルしたものはARMv6デバイスのみで動き,
ARMv7用にコンパイルしたものはARMv7デバイスのみで動く.

ARMv7 support on the latest devices would be pretty useless if you couldn’t take advantage of it, so you can do so, but always doing so would prevent your code from running on earlier devices, which may not be what you want. So how do you detect which architecture version a device supports so that you can take advantage of ARMv7 features if and only if they are present? The thing is, you don’t. Instead, your code is compiled twice, once targeting ARMv6, and once targeting ARMv7; the two executables are then put together in a fat binary, and at runtime the device will itself choose the best half it can support. Yes, Mach-O fat binaries are not just for grouping completely different CPU architectures (e.g. PowerPC and Intel, making a Universal Binary), or 32 and 64 bit versions of an architecture, but also two variants (cpu subtypes, in Mach-O parlance) of the same architecture. The outcome is that from the viewpoint of the programmer, everything gets decided at compile time: the code compiled targeting ARMv6 will only ever run on ARMv6 devices, and the code compiled targeting ARMv7 will only ever run on ARMv7 (or better).

NEONのポストで書いた通り,
ランタイムでの検知と選択をお勧めした.
今見てみると,
僕がその部分を削除したことに気がつくだろう.
過去ポストのその方法はおすすめできない.
これは動作はするが,将来のARMv8まで動くという確信が不可能なのだ.
(またはエラーが出ないようにトリッキーに移植するかだが)
実際,ドキュメント化されたそのAPIの状態がはっきりしないのだ.
(manページはiOSのものではない)
コンパイル時の選択とファットバイナリはARMv6とARMv7の場合のみにすべきだ.

If you’ve read my NEON post, you may remember that in that post I also suggested a way to do the detection and selection at runtime. If you check now, you’ll notice I have actually removed that part, and now recommend you do not use that method any more.1 This is because while it does work, it was impossible (or at the very least too tricky to implement to do so without any error) to ensure that the code would keep working the day it is run on a future ARMv8 processor. The fact the documented status of that API is unclear doesn’t help, either (its man page isn’t in the iOS man pages). You should exclusively use compile-time decision and fat executables if you want to run on ARMv6 and take advantage of ARMv7.

ノート:
iOSデバイスでは,ARMアーキテクチャのバージョンはARMプロセッサを厳密に意味するわけではない.
例えば,ARMv6が必要なiOSコードでは,
実際は浮動小数点命令が必要だ.
(VFPv2という名称)
これはARMv6のオプション機能だが,初代iPhoneから入っている.
だからARMv6とiOS開発で示す時は,
(例えば,コンパイラで-archやCPUサブタイプを設定するとき)
ハードウェアの浮動小数点演算サポートが暗示となる.
これはARMv7とNEONでも同様で,
NEONはARMv7プロファイルではオプションだが,
NEONはARMv7サポートのiOSデバイスには全て含まれている.
だからiOS開発では,NEONはARMv7の一部と考える.

One last note on the subject: in the context of iOS devices, the ARM architecture versions do not strictly mean what they mean for ARM processors in general. For instance, iOS code that requires ARMv6 actually requires support for floating-point instructions as well (VFPv2, to be accurate), which is an optional part of ARMv6, but has been present since the original iPhone, so when ARMv6 is mentioned in iOS development (e.g. as a compiler -arch setting or as the cpu subtype of an executable) hardware floating-point support is implied. The same goes for ARMv7 and NEON: NEON is actually an optional part of the ARMv7-A profile, but NEON has been present in all iOS devices supporting ARMv7, so when developing for iOS NEON is considered part of ARMv7.


状態付き実行
ARMアーキテクチャの気の利いた機能は,
ほとんどの命令が状態実行ができることだ.
状態付き実行とは,その条件がfalseの場合,その命令が効果をもたらさないものだ.
これにより,ブロックを効果的に実装できる.
普通のメソッドでは, 条件がfalseの場合ブロックの後にジャンプする.
その代わりに,ブロックが条件付きとなり,1つのブランチを保持している.

Conditional Execution

A nifty feature of the ARM architecture is that most instructions can be conditionally executed: if a condition is false, the instruction will have no effect. This allows short if blocks to be implemented more efficiently: while the usual method is to jump to after the block if the condition is false, instead the instructions in the block are conditionalized, saving one branch.

これがただのコード最適化のコンパイラの機能なら,書くつもりはなかった.
しかしこれは,デバッグ時に驚く程威力を発揮するのだ.
実際,デバッガでブロック内部を見て,どの条件がfalseか見る事ができる.
(例えば,初期エラーのリターンなど)
またはif-elseの両サイドに行く事もできるのだ!
これはプロセッサが実際にコードを全て読み込んでいるからだ.
しかしいくつかの部分は条件に合わない為に実際には実行されない.
さらに,ブロックの中にブレークポイントを置く事も出来る.
これは条件がfalseの時でもヒットする.

Now I wouldn’t mention this if it was just a feature the compiler uses to make the code more efficient; while it is that, I’m mentioning it because it can be surprising when debugging. Indeed, you can sometimes see the debugger go inside if blocks whose conditions you know are false (e.g. early error returns), or go in both sides of an if-else! This is because the processor actually goes through that code, but some parts of it aren’t actually executed, because they are conditionalized. Moreover, if you put a breakpoint inside such an if block, it may be hit even if the condition is false!

言われているように,これはコンパイラが条件付き実行命令をデバッグ状態ではさけているようだ.
(僕のテストにおいては)
つまりこれはデバックに最適化されたコードだけでおきる.
残念ながら,仕方なくやらなければいけない.

That being said, it seems (in my limited testing) that the compiler avoids generating conditionally executed instructions in the debug configuration, so it should only occur when debugging optimized code; unfortunately sometimes you have no choice but to do so.

Thumb
Thumb命令セットはARM命令セットのサブセットで,
16bitで実行されるように圧縮されている.
(全ARMアーキテクチャは32bitで,この時も32bitのままだ.命令が少ないスペースで実行できるのだ)
これは難しい構造ではない.
どちらかというと,普通のARM命令や機能の速記型に見える.
利点はもちろん,コードサイズの縮小,メモリ節約,キャッシュ,コードバンド幅だ.
またThumbはマイクロコントローラ型のアプリではとても便利だ.
メモリ節約でハードウェアを節約し,iOSの環境を出る事は無い.
これはXcodeでiOSプロジェクトを作成すると,デフォルトで利用できる.
コードのサイズが小さくなるとうれしいが,
2つのThumb命令は1つのARM命令とたまに同じサイズになる程度なので,
50%の圧縮には達しない.
ARMとThumb命令は自由に混ぜられるわけではない.
プロセッサがモードきりかえをする必要があるからだ.
これは関数呼び出しやリターン時に限定される.

Thumb

The Thumb instruction set is a subset of the ARM instruction set, compressed so that instructions take only 16 bits (all ARM instructions are 32 bits in size; it is still a 32-bit architecture, just the instructions take less space). It is not a different architecture, rather is should be seen as a shorthand for the most common ARM instructions and functionality. The advantage, of course, is that it allows an important reduction in code size, saving memory, cache, and code bandwidth; while this is more useful in microcontroller type applications where it allows hardware savings in the memory used, it is still useful on iOS devices, and as such it is enabled by default in Xcode iOS projects. The code size reduction is nice, but never actually reaches 50% as sometimes two Thumb instructions are required for one equivalent ARM instruction. ARM and Thumb instructions cannot be freely intermixed, the processor needs to switch mode when going from one to the other; this can only occur when calling or returning from a function.

ARMv6を対象にThumbをコンパイルするのは,大きなトレードオフとなる.
ARMv6 Thumbコードは多くのレジスタにアクセスできず,
条件付き命令も持っていない.
また,浮動小数点演算ハードウェアを利用できないのだ.
これはつまり,全ての単精度浮動小数点の和,差,積などで,Thumbコードはシステムファンクションを呼ばなければならない.
そう,これは遅いのだ.
この理由により,ARMv6ではThumbを無効にすることをお勧めする.
もし有効にしたままなら,コードのプロファイルを確認しよう.
もしいくつかの部分が遅い場合,まず1つの部分でThumbを無効にしよう.
(ファイル特有のコンパイラフラグをXcodeで使うのが簡単だ.-mno-thumb)
浮動小数点演算はiOSでは非常に一般的なことを覚えておこう.
QuartzとCore Animationは浮動小数点座標を使っている.

When targeting ARMv6, compiling for Thumb is a big tradeoff. ARMv6 Thumb code has access to less registers, does not have conditional instructions, and in particular cannot use the floating-point hardware. This means for every single floating-point addition, subtraction, multiplication, etc., floating-point Thumb code must call a system function to do it. Yes, this is as slow as it sounds. For this reason, I recommend disabling Thumb mode when targeting ARMv6. If you do leave it on, make sure you profile your code, and if some parts are slow you should first try disabling Thumb at least for that part (easy with file-specific compiler flags in Xcode, use -mno-thumb). Remember that floating-point calculations are pretty common on iOS since Quartz and Core Animation use a floating-point coordinate system.

ターゲットがARMv7の場合,この欠点は消える.
ARMv7はThumb-2を含んでいる.
これはThumb命令の拡張版で条件付き実行と,32bit Thumb命令をサポートしており,
全てのARMレジスタにアクセスできて,浮動小数点演算ハードウェアとNEONが使える.
これはコードサイズを減らすのに効果的なので,有効にしておくべきだ.
(もし無効にしていたら,有効に)
Xcodeの条件付き設定を使い,ARMv7の時は有効で,ARMv6の時は無効とする.

When targeting ARMv7, however, all these drawbacks disappear: ARMv7 contains Thumb-2, an extension of the Thumb instruction set which adds support for conditional execution and 32-bit Thumb instructions that allow access to all ARM registers as well as hardware floating-point and NEON. It’s pretty much a free reduction in code size, so it should be left on (or reenabled if you disabled it); use conditional build settings in Xcode to have it enabled for ARMv7 but disabled for ARMv6.

ネットのディスカッションでは,コードはThumbを内部で使うよう書かれている.
もし自分のコードを書いている限りでは,iOSの全てのコードが内部的であることを気にすることはない.
アセンブリを表示させ,SharkがARMかThumbかを判断できない場合は,
間違ったり,無意味な命令を探して,ARMかThumbに切り替える必要がある.

In discussions on the Internet you may find mentions that code needs to be “interworking” to use Thumb; unless you write your own assembly code, you don’t have to worry about it as all code is interworking in the iOS platform. When displaying assembly, Shark may have trouble figuring out whether the function is ARM or Thumb, if you see invalid or nonsensical instructions, you may need to switch from one to the other.

連携
iOSプラットフォームでは,非連携アクセスがサポートされている.
しかし連携アクセスに比べて遅いので,これをさけてみる.
特別なケースでは,非連携アクセスは同期アクセスに比べて数百倍遅い.
(これらはロード/ストア マルチ命令を含んでいる,興味があれば…)
プロセッサがこれらを扱えず,OSにアシストをお願いしなければいけないからだ.
(この記事を見てほしい.これはPowerPCでの同じ現象で,非同期が2倍遅くなっている)
だから気をつけてほしい.
同期はまだ問題なのだ.

Alignment

In the iOS platform unaligned accesses are supported; however, they are slower than aligned accesses, so try and avoid them. In some particular cases (those involving load/store multiple instructions, if you’re interested), unaligned accesses can be a hundred times slower than aligned accesses, because the processor cannot handle these and has to ask the OS for assistance (read this article, it’s the same phenomenon that on PowerPC causes unaligned doubles to be so much slower). So be careful, alignment still matters.

除算
これはいつも皆が驚く.
ARMアーキテクチャのマニュアルを開いて,
(もしまだ持っていなかったら,NEON on iPhoneの序文で,Architecture Overviewの章を見てほしい)
そして整数除算命令を探してほしい.
やってごらん.待ってるから.
見つからなかった?それが普通だ.無いのだ.
そう.ARMアーキテクチャは整数除算のハードサポートがないのだ.
これはソフトに撮って重要だ.
もし以下のコードをコンパイルすると,

Division

This one always surprises everyone. Open the ARM architecture manual (if you don’t already have it, see Introduction to NEON on iPhone, section Architecture Overview) and try and find an integer division instruction. Go ahead, I’ll wait. Can’t find it? It’s normal. There is none. Yes, the ARM architecture has no hardware support for integer division, it must be performed in software. If you compile the following code:

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

アセンブリで,コンパイラが“___divsi3”を挿入していることがわかるだろう.
これはシステムファンクションで,ソフトで除算を行うものだ.
(割る数が定数か,あるいはこの除算が積算に変換される)
これはARMの出来事だ.
整数除算はオープンシステムのベンチマークで重要だ!

to assembly, you’ll see the compiler has inserted a call to a function called “___divsi3”; it’s a system function that implements the division in software (notice the divisor should be non-constant, as otherwise the division is likely to be turned into a multiplication). This means that on ARM, integer division is actually an operating system benchmark!

「しかし」と言われるかもしれない.
ARMのマニュアルを見て.
「それは違う!ARMの除算命令は2つもあるじゃないか!sdivとudivだ!」
残念だが,それらの命令はARMv7-RとARMv7-Mのものだ.
(リアルタイムや組み込みでは,マイクロコントローラと腕時計想像してほしい)
iOSのARMv7はARMv7-Aで,非サポートだ.
残念!

“But!” you may say, having finally returned victorious from the ARM manual “You’re wrong! There is an ARM division instruction, even two! Here, sdiv and udiv!” Sorry to rain on your parade, but these instructions are only available in the ARMv7-R and ARMv7-M profiles (real-time and embedded, respectively – think motor microcontrollers and wristwatches), not in ARMv7-A which is the profile that the iOS devices that have ARMv7 do support. Sorry!

GCC
ARMコードがGCCで生成できる事は秘密ではない.
他のARMベースプラットフォームのプロの開発者はtoolchainをARMから提供してもらっている.
RVDSだ.
しかしiOSプラットフォームにおいて,RVDSはMach-0をサポートしておらず,
ELFのランタイムのみをサポートしている.
しかし,GCCの他に選択支もある.
LLVMはiOS開発で使用できる.
じっくりテストしていないが64bitの整数コードではLLVMで良い結果がでた.
(GCCより部分的に高速化された)
LLVMはそのうちGCCより全ての面で優れていることがわかるだろう.

GCC

It’s not a secret that the ARM code generated by GCC is considered to be crap. On other ARM-based platforms professional developers use the toolchain provided by ARM itself, RVDS, but this is not an option on the iOS platform as RVDS doesn’t support the Mach-O runtime used by OSX, only the ELF runtime. But there is at least an alternative to GCC, as now LLVM can be used for iOS development. While I’ve not tested it much, I have at least seen nice improvements on 64-bit integer code (a particularly weak point of GCC on ARM) when using LLVM. Hopefully, LLVM will prove to be an improvement over GCC in all domains.

さあ,あなたはiOS開発者として成長したよ!

~
There, you’re now a better iOS developer!

No comments: