NDK Android* アプリケーションの移植方法
この記事は、インテル® デベロッパー・ゾーンに掲載されている「NDK Android* Application Porting Methodologies」の日本語参考訳です。
はじめに
この記事では、既存の ARM* ベースの NDK アプリケーションを x86 に移植する際に役立つ情報を提供します。ARM* ベースで動作するアプリケーションがすでにあり、そのアプリケーションを x86 ベースの Android* 市場へ迅速に投入する方法を模索している開発者は、この記事をまずお読みください。移植時のコンパイラーの問題を解決するヒントとアドバイスも紹介します。
目次
NDK の概要
NDK は、強力なネイティブ x86 コードとラッパー Android* アプリケーションのグラフィカル・インターフェイスを組み合わせた優れたツールです。一部のアプリケーションはパフォーマンスが向上しますが、注意が必要な場合もあります。
NDK の目的は、次のとおりです。
- Android* パッケージで使用するネイティブ C/C++ ライブラリー (ラッパー Java* コード) をコンパイルする。
- 必要に応じて ARM* ネイティブ・ライブラリーを x86 (インテル® Atom™ マイクロアーキテクチャー) 用に再コンパイルして移植する。
上記の 2 点目は、オプションを変えて再コンパイルするだけで移植できるケースもありますが、簡単に移植できないケースもあります。例えば、ネイティブ・ライブラリーの C コード内にインライン・アセンブリーが含まれる場合、異なるアーキテクチャー向けのアセンブリーはそのままではコンパイルできず、コードの変更が必要になります (詳細は、「ARM* から x86 への移植のヒント」以降を参照)。
NDK でプリコンパイルされたネイティブコードと Android* Java* コードを連携するインターフェイスは、Java* Native Interface (JNI) と呼ばれます。JNI の詳細は、「Java Native Interface」を参照してください。
上記のページにある JNI の仕様は広範で詳細なため、Wiki ページの概要を参照するのも良いでしょう (Wiki ページの内容に疑問がある場合は、常に仕様を確認してください)。
JNI のオーバーヘッドは大きいので、理想的には、アプリケーションでの JNI 呼び出しは最小限にすべきです。Android* アプリケーションにネイティブコードを使用してもパフォーマンスが向上するとは限りません。一般に、プロセッサー処理 (負荷の高い SSE 命令の使用など) が多いネイティブコードはパフォーマンスが向上しますが、その他の場合、例えばユーザーに複雑な Web インターフェイスを提供するようなアプリケーションでは、JNI でネイティブコードを使用するとパフォーマンスが低下します。どのような場合に NDK を使用すべきか、すべきでないかについて「明文化されたルール」はありませんが、このような観点から一般的なガイドラインと考慮すべき点が分かります。
NDK の最新バージョンは、http://developer.android.com/tools/sdk/ndk/index.html (英語) から入手できます。NDK r6b の時点で、NDK は ARM* ベースと x86 (インテル® Atom™ マイクロアーキテクチャー) ベース両方のネイティブ・ライブラリーのビルドに使用できます。つまり、開発者は 1 つのパッケージでネイティブコードを移植することができます。
makefile
プロジェクト用の makefile として、Android.mk (およびオプションの Application.mk) ファイルを作成する必要があります。Application.mk ファイルにはアプリケーションで必要なネイティブモジュールを、Android.mk ファイルにはモジュール (スタティック/共有ライブラリー) のビルド方法を指定します。単純な Android.mk ファイルの一部を次に示します。
図 1: 単純な Android.mk ファイルの内容
ビルドシステムは、モジュール名の前に lib を追加し、libtest.so という名前のライブラリー を生成します。LOCAL_SRC_FILES にはソースファイルの名前、LOCAL_LDLIBS にはリンクフラグ、LOCAL_CFLAGS にはコンパイルフラグをそれぞれ指定します。
コマンドラインで x86 アーキテクチャーをビルドターゲットにするには、ndk-build APP_ABI=x86 のように指定します。
ネイティブ・ライブラリーは、System.loadLibrary(“relative_path_and_name”) および System.load(“full_path_to_lib_file”) の 2 つの方法で呼び出すことができます。1 つ目の方法がより一般的で、この方法では Android.mk ファイルで指定されたライブラリー名の “lib” 部分を省略できます。次に例を示します。
図 2: ネイティブコードの呼び出し例
一般的な C/C++ ヘッダーとは異なり、ネイティブコードの入力メソッドが JNIEXPORT メソッドのシグネチャーと一致していることをネイティブコード側で保証する必要があります。詳細は、前述の JNI リンクの情報を参照してください。
ネイティブ・ライブラリーは、Android* apk パッケージで提供して実行時に参照するか、Android* ファイルシステムにあるライブラリーの絶対パスを設定してロードできます。どちらの方法を選択するかは開発者次第です。選択した方法を適切に制御してください。
adb logcat コマンドを用いて、実行時にターゲットのネイティブ・ライブラリーが正常にロードされているかどうかを確認できます。ネイティブ・ライブラリーが正常にロードされたときのシステムログの例を次に示します。ネイティブ・ライブラリー・ファイルがフルパスで表示されていることに注意してください。
図 2: ネイティブコードの呼び出し例
この節では、NDK の使用法について簡単に説明しました。詳細については、NDK パッケージに含まれるドキュメントを参照してください。さまざまなアプリケーション用のチュートリアルとソースコード例が含まれています。
移植の概要
多くの場合、既存の NDK アプリケーションから x86 への移植は簡単に行えます。ネイティブコードが ARM* 固有の機能を使用していない限り、アプリケーションを再コンパイル、再パッケージ、再発行するだけです。
NDK アプリケーションを x86 へ移植する手順は次のとおりです。
- 最新の NDK ツールを入手します。X86 は最初に android-ndk-r6 でサポートされましたが、当初はいくつかの問題がありました。Android* NDK サイト (英語) から最新の (2014 年 4 月末時点の最新バージョンは android-ndk-r9d) をダウンロードしてインストールします。
- Application.mk ファイルを使用している場合は、APP_ABI 行に x86 を追加します。次に例を示します。
APP_ABI := armeabi armeabi-v7a x86
Application.mk ファイルを使用していない場合は、コマンドライン・ビルドの APP_ABI に x86 を追加します。NDK サンプル・アプリケーションのコマンドラインと出力を次に示します。
$ ndk-build APP_ABI=”armeabi armeabi-v7a x86″Install : test-libstl => libs/armeabi/test-libstl
Install : test-libstl => libs/armeabi-v7a/test-libstl
Install : test-libstl => libs/x86/test-libstl - 前のステップによって、各アーキテクチャーのバイナリーを含むフォルダーが libs ディレクトリー以下に作成されます。このステップでは、APK を再パッケージして新しいライブラリーを追加します。libs ディレクトリーはプロジェクトのルート・ディレクトリー以下にあるため、APK を作成するビルドツールは libs ディレクトリーにあるバイナリーを検出します。そのため、Eclipse* からプロジェクト APK をリビルドするだけで、新しい x86 ライブラリーを統合できます。コマンドラインでビルドする場合も同じです。サンプルデモ hello-jni をリビルドした場合のサンプル出力を次に示します。
$ android.bat update project –path C:/Tools/android-ndk-r6b/samples/hello-jni
Updated local.properties
Added file C:\Tools\android-ndk-r6b\samples\hello-jni\build.xml
Added file C:\Tools\android-ndk-r6b\samples\hello-jni\proguard.cfg
$ ant -f hello-jni/build.xml debug
Buildfile: C:\Tools\android-ndk-r6b\samples\hello-jni\build.xml
…
debug:
[echo] Running zip align on final apk…
[echo] Debug Package: android-ndk-r6b\samples\hello-jni\bin\HelloJni-debug.apk
BUILD SUCCESSFUL - このステップでは、リビルドしたアプリケーションをインテル® アーキテクチャーのデバイスまたは x86 エミュレーター上で実行してテストします。最後に、zip アーカイブツールで APK を開き、すべてのバイナリーが正しくパッケージされていることを確認します。x86 バイナリーを含む APK 構造のスクリーンショットを次に示します。
ARM* から x86 への移植のヒント
x86 へのアプリケーションの移植は簡単ですが、インテル® Atom™ と ARM* アーキテクチャーの違いを考慮する必要があります。次のトピックでは、アーキテクチャーの違いによって発生する可能性のある問題と対応方法について説明します。
Android* ビルドスクリプトの代わりに、ビルド環境でツールチェーンを直接使用することもできます。ARM* の場合、次のパスを使用します。
android-ndk\toolchains\arm-linux-androideabi-4.4.3
x86 の場合、次のパスを使用します。
android-ndk\toolchains\x86-4.4.3
詳細は、android-ndk/docs/STANDALONE-TOOLCHAIN.html の NDK ドキュメントを参照してください。
メモリー・アライメントの留意事項: ARM* とインテル® Atom™ マイクロアーキテクチャーの違い
ARM* とインテル® Atom™ マイクロアーキテクチャー間で C/C++ コードを移植するときに、メモリー・アライメントが一致しないことがあります。具体的な例は、http://software.intel.com/en-us/blogs/2011/08/18/understanding-x86-vs-arm-memory-alignment-on-android (英語) を参照してください。ここで重要なのは、開発者がコードを設計する際に、データのアライメントを明示的に行うよう考慮しなければいけないことです。アライメントを考慮しないと、異なるプラットフォーム間でデータが正しく処理される保証はありません。
浮動小数点: ARM* とインテル® Atom™ マイクロアーキテクチャーの違い
現在、NDK ライブラリーをビルドするときにサポートされているアプリケーション・バイナリー・インターフェイス (ABI) は 3 つあります。
- ‘armeabi’ – デフォルトの ABI であり、ARM* v5TE ベースのデバイスをターゲットにしたバイナリーを生成します。このターゲットの浮動小数点演算は、ソフトウェア浮動小数点演算を使用します。この ABI で作成されたバイナリーは、すべての ARM* デバイスで動作します。
- ‘armeabi-v7a’ – ARM* v7 ベースのデバイスをサポートし、ハードウェア FPU 命令を使用するバイナリーを生成します。
- ‘x86’ – ハードウェア・ベースの浮動小数点演算を含む IA-32 命令セットをサポートするバイナリーを生成します。
これらの ABI オプションはすべて、浮動小数点演算をサポートしています。ARM* 固有のアセンブリー命令を使用していない限り、x86 への移植で問題は発生しません。‘armeabi’ でコンパイルしていたコードで x86 をサポートすると、ほとんどの浮動小数点演算のパフォーマンスが向上します。
ARM* の NEON* 命令をインテル® SSE (インテル® Atom™ マイクロアーキテクチャー) へ移植
ここでは、インテル® アーキテクチャーと ARM* における SIMD 拡張命令の実装の違いについて、簡単に紹介します。ここでは、ツールを用いた単純なコーディングについて説明します。
NEON* は、主にスマートフォン、HDTV、その他のマルチメディア・アプリケーションで使われている ARM* テクノロジーです。ARM* のドキュメントでは、NEON* は 128 ビット SIMD エンジンベースのテクノロジー (ARM* Cortex*–A シリーズの拡張) であり、そのパフォーマンスは ARM* v5 アーキテクチャーの少なくとも 3 倍以上、その後継である ARM* v6 アーキテクチャーの 2 倍以上であると記載されています。NEON* テクノロジーの詳細とパフォーマンスの考慮事項については、http://www.arm.com/products/processors/technologies/neon.php (英語) を参照してください。
重要な点としては、ベクトルにレジスターが割り当てられ、各レジスターは同じデータ型のベクトル要素となることです。そして、パックド SIMD として知られる方法で、複数のレーンを同時に処理できます。
インテル® SSE とは、インテル® アーキテクチャー (IA) 向けのストリーミング SIMD 拡張命令です。インテル® Atom™ マイクロアーキテクチャーは現在、インテル® ストリーミング SIMD 拡張命令 4.2 (インテル® SSE4.2) とそれ以前のバージョンに対応しています。インテル® SSE は、浮動小数点データのパック処理を行う 128 ビットのエンジンです。その実行モデルはインテル® MMX® テクノロジーに端を発しており、SSEx はインテル® MMX® テクノロジーの後継と言えます。詳細は、『Intel® 64 and IA-32 Architectures Software Developer Manuals』 (英語) の「Volume 1: Basic Architecture」を参照してください。現在、5.5 節のインテル® SSE の概要に、SSE、SSE2、SSE3、SSSE3、SSE4.1、および SSE4.2 のオペコードの説明があります。これらの命令には通常、指定された精度のパックド浮動小数点値の操作が含まれており、XMM レジスター間または XMM レジスターとメモリー間でデータをまとめて移動できます。XMM レジスターは MMX レジスターの代わりに使用されます。
アセンブリー・レベルにおける NEON* とインテル® SSE の違い
前述の『Intel® 64 and IA-32 Architectures Software Developer Manuals』は、すべてのインテル® SSEx のクロスリファレンスとして使用できます。また、次の Web サイトに、インテル® SSE のアセンブリー・レベルの命令の説明があります: http://neilkemp.us/src/sse_tutorial/sse_tutorial.html (英語)。このサイトの目次から、コードサンプルを直接参照したり、背景情報などを得ることができます。また、ARM* から提供されている次の マニュアルにも、NEON* の情報とアセンブリー・コードのサンプルが含まれています: http://software.intel.com/sites/default/files/m/b/4/c/DHT0002A_introducing_neon.pdf (英語)。1.4 節を参照してください。
2013 年 11 月時点の NEON* とインテル® SSE のアセンブリー・コードの主な相違点を次に示します (テクノロジーの進歩、今後の SIMD テクノロジーやアプリケーションのコーディング上の問題などに応じてこの情報は変わる可能性があることに注意してください)。
エンディアン: インテル® SSE はリトルエンディアンのアセンブリーのみをサポートしており、ARM* はビッグエンディアンとリトルエンディアンをサポートしています (ARM* はバイエンディアン)。提供されているコード例では、ARM* のコードはインテルのコードと同じようにリトルエンディアンです。ARM* の場合、コンパイル時に留意事項がいくつかあることに注意してください。例えば、GCC* で ARM* 向けにコンパイルする場合は、–mlittle-endian および –mbig-endian コンパイラー・オプションを指定します。詳細は、http://gcc.gnu.org/onlinedocs/gcc/ARM-Options.html (英語) を参照してください。
粒度: 提供されている単純なアセンブリー・コードの例は、インテル® SSE の ADDPS 命令と NEON* の VADD.ix (x = 8 や 16 など) を比較しています。この例には、NEON* とインテル® SSE のすべての違いは含まれていないことに注意してください。この例から、NEON* では処理するデータの粒度を指定していることが分かります。
C/C++ レベルにおける NEON* とインテル® SSE の違い
C/C++ レベルの NEON* のコードをインテル® SSE に移植する場合、API に関してさまざまな留意事項があります。ここでは、インライン・アセンブリーは使われず、言語仕様に忠実な C/C++ コードが使用されることを前提としています。
高水準プログラミングにおける NEON* とインテル® SSE の違いには、大きなデータサイズ (128 ビット) の制御があります。移植の例は、http://stackoverflow.com/questions/7203231/neon-vs-intel-sse-equivalence-of-certain-operations (英語) を参照してください。
まとめ
この記事では、既存の ARM* ベースの NDK アプリケーションを x86 に移植する際に役立つ情報を紹介しました。x86 に移植することで、インテル® アーキテクチャー・ベースの Android* デバイスでアプリケーションを使用できるようになります。移植作業中に問題が発生した場合は、この記事にコメントを投稿していただければ回答します。
著作権と商標について
* その他の社名、製品名などは、一般に各社の表示、商標または登録商標です。
© 2014 Intel Corporation. 無断での引用、転載を禁じます。
Intel、インテル、Intel ロゴ、Intel Atom、MMX は、アメリカ合衆国および / またはその他の国における Intel Corporation の商標です。
本資料に掲載されている情報は、インテル製品の概要説明を目的としたものです。本資料は、明示されているか否かにかかわらず、また禁反言によるとよらずにかかわらず、いかなる知的財産権のライセンスを許諾するものではありません。製品に付属の売買契約書『Intel’s Terms and Conditions of Sale』に規定されている場合を除き、インテルはいかなる責任を負うものではなく、またインテル製品の販売や使用に関する明示または黙示の保証 (特定目的への適合性、商品適格性、あらゆる特許権、著作権、その他知的財産権の非侵害性への保証を含む) に関してもいかなる責任も負いません。
インテルによる書面での合意がない限り、インテル製品は、その欠陥や故障によって人身事故が発生するようなアプリケーションでの使用を想定した設計は行われていません。
インテル製品は、予告なく仕様や説明が変更される場合があります。機能または命令の一覧で「留保」または「未定義」と記されているものがありますが、その「機能が存在しない」あるいは「性質が留保付である」という状態を設計の前提にしないでください。これらの項目は、インテルが将来のために留保しているものです。インテルが将来これらの項目を定義したことにより、衝突が生じたり互換性が失われたりしても、インテルは一切責任を負いません。この情報は予告なく変更されることがあります。この情報だけに基づいて設計を最終的なものとしないでください。
本資料で説明されている製品には、エラッタと呼ばれる設計上の不具合が含まれている可能性があり、公表されている仕様とは異なる動作をする場合があります。現在確認済みのエラッタについては、インテルまでお問い合わせください。
最新の仕様をご希望の場合や製品をご注文の場合は、お近くのインテルの営業所または販売代理店にお問い合わせください。
本資料で紹介されている資料番号付きのドキュメントや、インテルのその他の資料を入手するには、1-800-548-4725 (アメリカ合衆国) までご連絡いただくか、インテルの Web サイトを参照してください。
コンパイラーの最適化に関する詳細は、最適化に関する注意事項を参照してください。