NDKを使ってみた
もちろん、NDKを使ってライフゲームをもっと早くするというお話です。
まず、最初にやったのはNDKのセットアップ、
「Android ndk」とかで検索すれば、その手順が出てきます。
自分の場合はホームディレクトリに”Develop”フォルダを作って、
そこにまとめてインストールしています。
~/Develop ├── android-ndk-r6b ├── android-sdk-linux_x86 ├── eclipse └── workspace
この”android-ndk-r6b”が、現在使用してるNDKです。
なので、”.bashrc”には、以下の記述を追加しています。
export PATH=${PATH}:~/Develop/android-sdk-linux_x86/tools
export PATH=${PATH}:~/Develop/android-ndk-r6b
あとは、
$ ndk-build -v
とかでパスが通ったことを確認できれば準備完了です。
次に、EclipseにCDTをインストール(*1)するということ。
これは、「Eclipse CDT」で検索してください。
これによって、EclipseでCソースやヘッダーファイルの編集が可能になります。
あと、”Android.mk”(NDKにおけるMakefile)も色分けされます。
1. Cで実装する処理の選定
まず、どこを高速化するかですよね。
ライフゲームの場合は選択肢がないので、
誕生と消失を行う関数をCソースに移植します。
2. ByteBufferによる書き直し
さっそくCで書き直す前に、ByteBufferで実装し直します。
というのも、Javaの配列をそのまま渡してしまうと、
引数として受け取ったあとにCで使える配列に変換する必要出てしまいます。
このオーバーヘッド(*2)をなくすためにByteBufferに置き換えます。
3. Cで書き直す
Cで書き直すにもファイルをどこに置くかですよね。
“AndroidManifest.xml”がある階層に、”jni”フォルダを用意します。
その中に、Cソースとヘッダーファイル、そして”Android.mk”を置きます。
jni ├── Android.mk ├── lifegamelib.c └── lifegamelib.h
ちなみに、ByteBufferを引数で渡すと、こんな感じで先頭アドレスを確保出来ます。
void Java_your_domain_LifeGameWallpaper_LifeGame_ndkExecLifeGame( JNIEnv* env, jobject thiz, jobject buf, jint w, jint h) { // ByteBufferを配列の先頭アドレスとして確保 unsigned char *p = (*env)->GetDirectBufferAddress( env, buf ); // 処理を書く }
それと、自分が作成した”Android.mk”はこんな感じです。
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_SRC_FILES := lifegamelib.c LOCAL_MODULE := lifegamelib include $(BUILD_SHARED_LIBRARY)
4. ビルドする
端末(コンソール?)を使って、”AndroidManifest.xml”がある階層で、
$ ndk-build
これだけデス。(*3)
これで、”libs”フォルダが作られて、その中にさらにフォルダが作られて、
上記の例だと、”liblifegamelib.so”が生成されます。
こんな感じで、先頭に”lib”が付いて、拡張子に”.so”がくっ付きます。
5. ライブラリを使用する
このライブラリを使うクラスでは、以下のように宣言します。
package your.domain.LifeGameWallpaper; import java.nio.ByteBuffer; public class LifeGame { private final byte STATE_DEAD = 0, STATE_STAY = 1, STATE_BORN = 2; private int _width; private int _height; private byte[] _buf; private ByteBuffer _directBuffer; // ライブラリを読み込むための記述 static { System.loadLibrary( "lifegamelib" ); } // ライブラリ内の関数を参照するための記述 private native void ndkExecLifeGame(ByteBuffer buf, int w, int h); // 既存のメソッド定義等 }
あとは、ndkExecLifeGameを適所で呼び出すだけです。
まとめ
まずは、自分がハマったポイントから。
- 生成されるライブラリは”liblifegamelib.so”だけど、
loadLibraryの引数は”lifegamelib”と書く。 - Cソースにおける関数名は、
“Java_package_class_method”のように定義しないとJava側で呼び出せない。
という訳で、まずloadLibraryでハマって、関数名の記述ミスでハマりました。
あと、どの程度早くなったのかというと、
ライフゲームの処理が10msくらい掛かっていたのが、3msくらいになりました。
Javaのコードをコピペして、コンパイルが通る程度に手を入れた訳ですが、
十分に効果がありました。
ただ、まだまだ無駄なメモリコピーがあって、
あと、入力と出力の計2つByteBufferを渡さなきゃいけないところを、
1つのByteBufferの前半と後半を使って処理してたりと、
人様に見せられない恥ずかしい実装なので、コードはちょっと晒せないデス。
それと、NDKの導入は考えてなかったので、
“liblifegamelib.so”という恥ずかしいファイル名になってしまって、
これも本来なら晒したくなかったデス。
あと、メモとして、
Android側のコードに変更がないと、ライブラリの変更が反映されないらしいです。
今後の予定は以下の通り。
- ライブ壁紙の設定を実装する
- 描画周りが5msくらい掛かっているので最適化する
- 計測方法を検証する(*4)
(*1) CDTのインストールをしなくてもNDKを使った開発はできます。
(*2) どの程度オーバーヘッドが発生するのかは未検証デス、ゴメンなさい。
(*3) ビルドオプションは、自分で検索してください。
(*4) デバッグのためにケーブルを接続した状態だと遅いらしい(?)
おしまい。
Leave a Comment