2012年11月5日月曜日

Android開発でのパフォーマンスTips

googleは公式ドキュメントとしてAndroid開発におけるパフォーマンス設計のベストプラクティスを公開しています。
Designing for Performance | Android Developers

どんなに優れたUIであってもパフォーマンスの悪いアプリはユーザにとって使いにくいものになってしまいます。
アプリのパフォーマンスチューニングは重要です。

今回は公式ドキュメントをベースにパフォーマンス設計のベストプラクティスのご紹介です。
Javaの世界で当たり前だったものが、Androidの世界では通用しないものも多々あるので覚えておきたいです。

基本的な考え方

Androidで良好なパフォーマンスを得るための基本的な考え方は

  • 無駄な処理はしない
  • 無駄なメモリは使わない

ことです。以降に出てくる原則はすべてこれにのっとったものです。

オブジェクト生成は避ける

オブジェクトを生成するとメモリ割り当てが行われるので、最終的にGCが走り、処理落ちしますよ、ということですね。
できる限りオブジェクト生成しない方法で実装するのが望ましいです。

  • 文字列抽出を行う場合は、文字列コピーをせず、substringで取り出す
  • 文字列加工はStringBufferを使い、一時的なStringオブジェクト生成を避ける
  • Integerは使わずintを使う。可能な限りプリミティブで

などなど。

ネイティブメソッドを使う

文字列操作をする時、String.indexOf()やString.lastIndexOf()などのC/C++でネイティブ実装されているメソッドを使うことで、10〜100倍パフォーマンスに違いが出ることがあるので、できる限りネイティブ実装のメソッドを使うこと。

インターフェースは使わない

  1. Map myMap1 = new HashMap();  
  2. HashMap myMap2 = new HashMap();  

オブジェクト指向になぞらえば、上記のようなケースでは多様性の恩恵を受けるために、Mapインターフェースを使った実装が好まれるが、Androidの実装ではパフォーマンス的にNG。インターフェースを使う場合、使わない時と比べて2倍近く遅くなることがあります。
ただし、外部へAPIを公開する場合は、パフォーマンスを犠牲にしてでもインターフェースを使うべきケースも出てくるので利用シーンに応じて最適な選択をすべき。

スタティックメソッドを使う

オブジェクトのフィールドにメソッド内でアクセスする必要がない場合、メソッドはスタティックにすべき。
仮想メソッドテーブルを捜査するコストがなくなるので高速になります。

クラス内部でgetter/setterは使わない

これまでJavaに慣れ親しんだ人は、フィールドにアクセスする場合、直接操作せず、getter/setterを使う習慣が身についていると思うが、パフォーマンスの観点から見ると、getter/setterを呼ぶよりもフィールドに直接アクセスした方が高速なので、クラス内部でフィールドを参照する場合は、直接フィールドへアクセスすべき。

ローカル変数でキャッシュする

直接フィールド参照するよりも、ローカル変数を参照する方が高速なので、何度も参照する必要があるフィールドの場合、ローカル変数にキャッシュすべき。

  1. for (int i = 0; i < this.mCount; i++)  
  2.       dumpItem(this.mItems[i]);  

この場合、mCountフィールドとmltems[i]をループ回数分参照してしまうので、

  1. int count = this.mCount;  
  2. Item[] items = this.mItems;  
  3.   
  4. for (int i = 0; i < count; i++)  
  5.     dumpItems(items[i]);  

あらかじめ上記のようにローカル変数にキャッシュすると、無駄なフィールド捜査走査が走らずにすみます。

メソッドの引数もローカル変数と同様の特性があります。

定数はファイナルで宣言する

  1. static int intVal = 42;  
  2. static String strVal = "Hello, world!";  

上記のようにfinalなしで定数を定義した場合、intValを参照するときにフィールド走査が実行されるが、

  1. static final int intVal = 42;  
  2. static final String strVal = "Hello, world!";  

このようにfinal宣言をするとintValは直接VMが参照できるようになるため、高速になる。

メソッドやクラスに関してはfinal宣言をつけてもコード保守の面では効果があるが、パフォーマンス面でのメリットはない。
また、ローカルメソッドについてもパフォーマンス面では効果はないので要注意。

foreachループは気をつける

  1. public static void two() {  
  2.     int sum = 0;  
  3.     for (Foo a: mArray) {  
  4.         sum += a.mSplat;  
  5.     }  
  6. }  

上のようなjava1.5から加わったforeach構文を使う時、配列[]のループの場合は配列オブジェクト、長さをコンパイラがローカル変数にキャッシュするようなコードにしてくれるが、CollectionsのようなIterableなオブジェクトの場合、一時的なオブジェクトが暗黙的に生成されてしまうので、Iterableなオブジェクトのループの場合は、従来のfor文でローカル変数でキャッシュさせるのが吉。

enumは使わない

enumは便利だが、サイズとパフォーマンス面ではちょっとキツイ。

  1. public class Foo {  
  2.    public enum Shrubbery { GROUND, CRAWLING, HANGING }  
  3. }  

上記のようなenumを定義しただけで、900 byteのclassファイルが生成され、実行時にはそれぞれの列挙子に対して、クラスの初期化が実行されてしまう。これらはstatic finalなintに置き換えることが可能なので、できる限りenumは使うべきではないが、これも利便性とのトレードオフで、APIとして用意しておきたい場合は、enumを使った方がよいだろう。
ただ、その時はordinal()メソッドを使うと多少はパフォーマンス的によいかもしれない。
たとえば、

  1. for (int n = 0; n < list.size(); n++) {  
  2.     if (list.items[n].e == MyEnum.VAL_X)  
  3.        // do stuff 1  
  4.     else if (list.items[n].e == MyEnum.VAL_Y)  
  5.        // do stuff 2  
  6. }  

上のようなコードを

  1. int valX = MyEnum.VAL_X.ordinal();  
  2. int valY = MyEnum.VAL_Y.ordinal();  
  3. int count = list.size();  
  4. MyItem items = list.items();  
  5.   
  6. for (int  n = 0; n < count; n++)  
  7. {  
  8.      int  valItem = items[n].e.ordinal();  
  9.   
  10.      if (valItem == valX)  
  11.        // do stuff 1  
  12.      else if (valItem == valY)  
  13.        // do stuff 2  
  14. }  

こんな形に直すとよい。
常にこちらが早くなるという保障はないけど基本的には高速なはず。

インナークラス利用時はパッケージスコープを

たとえば、こんなインナークラスを使ったコードがある。

  1. public class Foo {  
  2.     private int mValue;  
  3.   
  4.     public void run() {  
  5.         Inner in = new Inner();  
  6.         mValue = 27;  
  7.         in.stuff();  
  8.     }  
  9.   
  10.     private void doStuff(int value) {  
  11.         System.out.println("Value is " + value);  
  12.     }  
  13.   
  14.     private class Inner {  
  15.         void stuff() {  
  16.             Foo.this.doStuff(Foo.this.mValue);  
  17.         }  
  18.     }  
  19. }  

この場合、インナークラスInnerのstuff()メソッドは正しく"Value is 27″を表示するが、Fooクラスのprivateなフィールド、メソッドへアクセスしているため、コンパイラは以下のようなメソッドを生成し、これらを使ってアクセスを試みる。

  1. /*package*/ static int Foo.access$100(Foo foo) {  
  2.     return foo.mValue;  
  3. }  
  4. /*package*/ static void Foo.access$200(Foo foo, int value) {  
  5.     foo.doStuff(value);  
  6. }  

Innerクラス内ではFooクラスのフィールド、メソッドを直接参照している風だが、実際には上記のアクセサメソッドを介して参照されるため、パフォーマンスが下がる。

こうしたパフォーマンス劣化を防ぐために、Fooクラスのフィールド、メソッドをprivateではなく、パッケージスコープ(修飾子なし)に置き換えることだ。

ただし、これも利便性とはトレードオフなので設計時に要検討。

floatは使わない

Pentium CPUの浮動小数点プロセッサが登場してからはデスクトップマシン等では、純粋な整数同士の演算よりも浮動小数点を含んだ演算の方が高速になったが、こと組み込み系に関しては未だに浮動小数点演算をサポートしていないことが多々ある。そうした場合、浮動小数点演算はソフトウェア的に処理されるためパフォーマンス劣化につながる。
だもんで、floatはやめましょう。

パフォーマンスサンプル値

これは表なので公式ドキュメントに譲ります。
Designing for Performance | Android Developers

まとめ

パフォーマンスを改善するポイントは多々あるけど、規模や設計と照らし合わせて最適なチョイスをしましょうねということになりそうです。

0 件のコメント:

コメントを投稿