Android アプリ開発独習メモ #5 — パフォーマンスに関するチップス
Google による Android アプリ開発のガイドを読み進めていくシリーズ。 今回は “Best Practices for Performance” の中の “Performance Tips” を読んだ。 ざっくりとまとめておく。
パフォーマンスに関して神経質になる必要はないと思うけど、それなりに気を付けないとスクロール時にガタガタしたりしてしまったりするので、一通り知識としては頭に入れておきたいところだと感じた。
Performance Tips
ミクロなレベルでの最適化の話。 当然ながら正しいアルゴリズムやデータ構造を選択するのは大事! (今回はそういう話ではない。)
効率のよいコードを書くための基本的な規則は次の 2 つ:
- 必要ない処理をしない。
- 避けられるならメモリ割り当てを行わない。
様々な種類のデバイスで実行することを考えると、VM のバージョンの違いによって、あるデバイスでは効率の良いコードであっても別のデバイスではそうでないこともある。 JIT の有無によっても効率の良いコードが何かは違ってくる。 エミュレータでの計測では、パフォーマンスについて、本当に少しのことしかわからない。
Avoid Creating Unnecessary Objects
オブジェクトの生成はできるだけ避ける、ということ。 例えば以下のような例。
- 文字列を返すメソッドがあったとして、そのメソッドの返り値が常に
StringBuffer
オブジェクトに追加されるのであれば、そもそものメソッドのシグニチャを変更して、引数としてStringBuffer
オブジェクトを受け取って、メソッドの中で追加するようにすればよい。 (そうすれば返り値としてのString
オブジェクトの生成が避けられる。) Integer
型の配列よりも、int
型の配列の方がよい。- 2 次元配列 (配列の配列) を作るよりも、1 次元配列で代替できるならばその方が良い。
Foo
オブジェクトとBar
オブジェクトの組を配列として保持したい場合、Foo
オブジェクトとBar
オブジェクトの組を表す独自のクラスを作り、そのクラスの配列を生成することも考えられるが、それよりもFoo
の配列とBar
の配列をそれぞれ別に作って管理した方が良い。
一般化すると、短期的に使用される一時的なオブジェクトの生成は避けるべし、ということ。 オブジェクトの生成が少ないと、それだけガベージコレクションの頻度が小さくなるので、ユーザー体験に影響する。
基本的に保守しやすさとパフォーマンスのトレードオフだと思うけど、PC と比べてリソースが少ないわけなので、PC 向けにプログラミングするときよりもリソースの消費には気を使わなければならない、というところか。
Prefer Static Over Virtual
オブジェクトのフィールドへのアクセスが必要ないメソッドならば、インスタンスメソッドではなく static メソッドにすべき、という話。 呼び出しが 15 % から 20 % ほど速くなるらしい。 メソッド呼び出しのコストが気になる場所ってそんなあるのかどうかわかんないけど。
『Prefer Static Over Virtual』 って見出しになってるけど、これってつまり virtual かどうかが重要なのであって、static かどうかが重要ってわけでもないのかな。 (つまり、final メソッドなら static じゃなくても速いのだろうか。)
Use Static Final For Constants
定数変数は static かつ final にしておきましょう、って話。
そうすることで、変数が使っている箇所に値が直接埋め込まれるので、クラス初期化用の <clinit>
ってメソッドが必要なくなる *1 し、変数のルックアップのコストもなくなる。 プリミティブ型と String
型にしかこの最適化は適用されないけど、他の型についても、定数は static かつ final にしておいた方がいい。
Avoid Internal Getters/Setters
C++ や C#、Java のベストプラクティスとして、直接的なフィールドアクセスよりも setter/getter を使った方が良い、というものがあるが、Android ではそれはしない方が良い。 virtual メソッドの呼び出しはインスタンスフィールドのルックアップよりもコストが大きい。
公開インターフェイスとして setter/getter を用意するのは良いが、例えばクラス内で使用する場合は直接フィールドアクセスするべきとのこと。 JIT 無しの場合は単なる getter よりも直接的なフィールドアクセスの方が 3 倍速く、JIT 有りの場合は 7 倍速いらしい。
ProGuard を使う場合は、それがアクセサのインライン化をしてくれるので、setter/getter を使っても直接的なフィールドアクセスでも良い、みたい。
Use Enhanced For Loop Syntax
for-each ループを使った方が、手書きによるカウントアップの for ループより速い場合が多いので、基本的に for-each ループを使いましょう。
int sum = 0; for (Foo a : mArray) { sum += a.mSplat; }
ただし、ArrayList
に関しては手書きによるカウントアップの for ループの方が for-each ループの 3 倍速いらしい。
Consider Package Instead of Private Access with Private Inner Classes
内部クラスからエンクロージングクラスの private メソッドを呼び出したりや private フィールドにアクセスしたりするのは、パッケージプライベートなメソッドを呼び出したり、パッケージプライベートなフィールドにアクセスしたりするよりもコストがかかる。 *2
なので、パフォーマンス的に重要な場所で内部クラスからエンクロージングクラスの private メンバにアクセスしている場合は、その private メンバをパッケージプライベートに変更すると良い。
Avoid Using Floating-Point
浮動小数点数より整数値の方が速い! (ので、整数値でいいところは整数値を使おう。)
Know and Use the Libraries
JIT 用に最適化されてたりするので、ライブラリメソッドがあるならそれを使おう。
String#indexOf
メソッド とそれ関連とか、System.arraycopy
メソッド とか。
Use Native Methods Carefully
Android NDK を使ってネイティブコードを書くのは、主には既存のコードを Android アプリ用に移植するためのものであって、Java で書かれたアプリの高速化のためのものではない、とのこと。
Performance Myths
JIT 無しのデバイスでは、実際の型の変数を通してのメソッド呼び出しの方が、インターフェイス経由のそれよりもわずかに効率が良い。 JIT 有りの場合は区別できない。
JIT 無しのデバイスの場合、フィールドの値を局所変数にキャッシュするとフィールドアクセスを繰り返すよりも約 20 % 高速である。 JIT 有りの場合は局所変数へのアクセスもフィールドへのアクセスもコストは変わらない。 なので、変更によりコードが読みやすくなると考えられないならば、やる価値はないだろう。
Always Measure
最適化するときには、解決すべき問題があることを認識しておかなければならない。 (やみくもにパフォーマンスを上げようとするのではなく、遅い部分があるからこそ最適化しようとするものだ、ってことだと思う。) 変更前のパフォーマンスを計測することができて、変更して比較できるように。
この資料で述べられていることは、全てベンチマークによる裏付けがある。 それらのベンチマークは code.google.com "dalvik" project にある。
これらのベンチマークは Caliper という Java 用のマイクロベンチマークのためのフレームワークを使って行われた。 マイクロベンチマークを正しく行うのは難しく、Caliper は役立つものであるとのこと。 マイクロベンチマークには Caliper を使うのがオススメらしい。
Traceview というのもプロファイリングに便利である。 ただし、現在のところこれは JIT を無効化することを認識しておく必要がある。
プロファイリングやデバッギングに役立つ文書:
まとめ
Effective Java 第2版 (The Java Series)
- 作者: Joshua Bloch,柴田芳樹
- 出版社/メーカー: ピアソンエデュケーション
- 発売日: 2008/11/27
- メディア: 単行本(ソフトカバー)
- 購入: 77人 クリック: 936回
- この商品を含むブログ (263件) を見る
細かい話が多いが、普段開発をするうえではそこまで神経質にならなくてもいいのではないかなーという感じがした。 (例えば内部クラスからエンクロージングクラスの private メンバにアクセスする話とか。)
しかし、例えば描画処理をするごとにメモリ割り当てをしてしまうと、ガベージコレクションが頻発してスクロール時にガタガタしてしまう、というようなことがあるので、パフォーマンスのチップスにはちゃんと身に付けて、避けるべき部分はしっかり避けていかなければならないと思う。