950 軽量化と高速化
携帯Javaにおいて大規模なアプリを作ろうと思うと必ず問題に挙がってくるのが、サイズが大きい 処理が遅い の2つの問題です。
このコンテンツでは、Vアプリ作成の際における軽量化高速化の手法を取り上げて行きたいと思います。

まずは、サイズの縮小についてのイントロダクションです。
各型の容量の限界を以下の表に表しておきます。
JARファイルサイズ RecordStoreサイズ 合計(jar+jad+RecordStore)サイズ
C4型 50KByte以内 50KByte以内 50KByte以内
P4型 80KByte以内 50KByte以内 100KByte以内
P5型 200KByte以内 200KByte以内 256KByte以内
P6型 200KByte以内 200KByte以内 256KByte以内
上記のように、非常にプログラムとリソース容量にかなりの制限があります。
大規模アプリの場合、この制限の内に収まるようにサイズを減らす努力をしなくてはなりません。
そうでないアプリでも容量を減らすことはパケット代の節約にもなりますので、どの場合もサイズ縮小に取り組んだ方がよいでしょう。

次に高速化についてです。
当たり前ですが携帯はパソコンとは比べ物にならないぐらい非力なCPUを積んでいます。
例として日立の携帯向けCPU SH-Mobileシリーズを挙げてみます。
製品名 SH-Mobile1 SH-MobileV SH-Mobile2 SH-Mobile3
登場年度 2002年 2003年 2004年 2005年
動作周波数 133Mhz 173Mhz 173Mhz 〜300Mhz
処理性能 173MIPS 173MIPS 173MIPS 〜540MIPS
内蔵RAM 128KByte 128KByte 128KByte 非公開
キャシュメモリ 32KByte 32KByte 32KByte 非公開
例えば、現在のパソコン(Pentium4 3GHz)と比べると、処理性能は1/20以下なので、非常に弱いCPUを積んでいることになります。
さらに、その上でJavaKVMを稼動させているので、ネイティブな機械語に比べて、とてつもなく処理が遅くなります。
この辺りが携帯ゲーム機と大きく異なる所です。
パソコンのエミュレーター上で快適に動くと思って携帯に転送してみたら、あまりに処理が遅くてゲームにならない なんてことはザラにあります。
その場合、高速化が必要になってきます。
高速化の手法の理念としては、無駄なコードを省き、JAVAコンパイラがより高速なコードを吐くようにコーディングすることです。
処理は早いにこしたことがないので、なるべく高速化なコーディングセンスを身につけましょう。


なお、サイズ縮小と高速化の方法を突き詰めると、ソースコードが読みづらくなり(可読性を悪くなり)、ソースコードの保守管理も大変になってきます。
そのため、まずは、何もソースコードに変更点を加えずにツールでボタン1発で自動でやってくれるもの(オブファスケーターなど)を優先して行うのが良いです。
さらに突き詰めるなら、一度コードを読み直し、最適化の方法に従って無駄を省く処理に書き直すのが良いです。
最も簡単に導入が出来、かつ効果の高い方法です。まずはこれをお試しください。
ツール編
■オブファスケーターを使う。
最もお手軽に、かつ軽量化と高速化の高い効果が得られます。
javaのソースコードが約15〜20%ほど容量が減ります。
オブファスケーターの使い方は、940 オブファスケーターを使う をご覧ください。
ProGuardを例に、オブファスケーターが内部的にどのような処理を行っているのかを記述します。
  • フィールド変数、クラス、メソッド、クラス変数の名前を短いアルファベットに置き換える。
  • デバッグ情報を削除。
  • コンスタントブールの再評価。
  • 実行されないコードの削除。
  • 使われていないクラスの削除。
  • 使われていないフィールド変数の削除。
  • 呼ばれていないメソッドの削除。
  • 不必要な分岐の削除。
  • 不必要な比較の削除。
  • 書き込みのみのフィールドの削除。
  • PUSH/POPのような単純な命令の最適化。
  • 可能な場合、クラスの属性をfinalにする。
  • 可能な場合、単純なset〜/get〜メソッドのインライン化。
  • 単一のインプリメンテーションを行っているインターフェースの交換。
  • logging codeの削除。
■7-zipを使ってより圧縮率の高いjarファイルにする。
ZIPと互換性があり、ZIPより高い圧縮をする7-Zipというソフトを使います。
http://www.7-zip.org/
元のjarに比べて、1〜2%ほど高く圧縮されます。
■PNG画像の最適化と無駄なチャンクを取り除く。
MSペイントやEDGEやPhotoshopなどで作成された、PNG画像にはさまざまな無駄な情報が入っています。
例えば、最終更新時間や画像のタイトル、作者、作成ソフトウェアなどです。
これらは普通にLoadImageする際には必要無い情報なので、その情報を削除するツールがあります。
さらに、PNGはzipと同様のinflate圧縮が行われており、7zipのようなより高い圧縮方法を取ることで、サイズの縮小を図れます。
これらの処理を行ってくれるツールとしては、
PNGGauntlet
最適なinflate圧縮と無駄なチャンクを取り除くの両方を行ってくれます。オススメ。
PNGcutdown 無駄なチャンクを取り除くことだけを行います。最大150バイト削減できます。再圧縮はされません。
■プリプロセッサを使って、定数展開や無駄なコードの削除をする。
C/C++使いなら当たり前の話ですが、Javaにはプリプロセッサがありません。(何で無いのかはたはた疑問ですが・・・)
今まで、static final で宣言されていたフィールド変数を、定数として変換することで変数を1つ減らすことができサイズ縮小になります。例えば、
static final int SCREEN_WIDTH 120; →(を)→ #define SCREEN_WIDTH 120
とし、プリプロセスを利かせ、定数に置き換えをします。

また、場合によって機種ごとに必要の無い命令を削除したり、エミュ上と携帯端末上で必要な処理を減らすことも可能です。
例えば、エミュレーター上ではデバッグコードとしてSystem.out.println()を良く使いますが、実機では必要が無い無駄コードとなりますので、
#ifdef _DEBUG
  System.out.println("〜〜〜〜");
#endif
とし、プリプロセッサオプションで、_DEBUG付け外しをすれば、状況に応じて無駄なコードを削除することができます。
これらの処理を行ってくれるツールとしては、
PPP フリーのプリプロセッサ。これで必要十分な機能は揃っている。
PP4J シェアウェア(\1050)。使ったことは無いがマニュアルを見る限りPPPと同機能。
などが挙げられます。

ソースコードを弄って、最適なJavaコードを生成するようにします。ほとんどがオブファスケーターが置換してくれると思いますが、
しかし、オブファスケーターがどこまでやってくれるのかは、ブラックボックスなので、なるべくプログラミングの時にこれらのことに気をつけてコーディングしてみてください。
Javaコーディング編
■クラス数を少なくする。
プロジェクトで作成するクラス数は1〜2つに抑えます。
ゲームなどでは、MIDletを継承したクラスと、Cavasを継承したクラスのみで作るのが理想です。
また、属性はfinalを付けると、若干(5byte程度)小さいサイズになります。
1クラス増やすと2Kbyteほど増えるといわれていますので、なるべくクラスを作らないようにしましょう。
■メソッド数を少なくする。
メソッドを増やすごとに30byteほど増えるために、極力作らないようにします。
しかし、同じような処理が沢山ある場合はメソッドを作った方が効率が良くなります。
(私の場合は呼び出し回数が3回を超え、3命令ほどの集合の場合にはメソッドにします)
また、メソッドはpublicで宣言することで、5byteほど軽くなります。
■変数を少なくする。
変数は可能な限り少なくします。
グローバル変数はpublic staticとし、なるべく定義をしないようにします。
フィールド変数の場合もpublic staticとし、なるべく定義しないようにします。
static finalで定義された定数は、プリプロセッサを使って定数で展開します。
変数の再利用をするのもいいと思います。(これは可読性がそこなわれてバグの温床になりますので注意してください)
他にテクニックとしては、例として0〜255までの大きくない整数値をとる4つの変数は、まとめて、1つのint型変数内でビットシフト演算子や論理演算子を用いて展開します。
■配列を使う。
変数は配列を使ってまとめることで、容量が少なくなります。
配列は1次元配列を使います。(多次元配列は1次元に展開してください)
配列は1つずつ初期値を入れず、プログラム内で展開するようにします。
例えば
int[] array = new int[5];
×な例
array[0] = 0;
array[1] = 1;
array[2] = 2;
array[3] = 3;
array[4] = 4;
○な例
for (int i=0;i<5;i++)
 array[i] = i;
非常に大きな配列でプログラム内で展開できないような不規則な値の配列の場合は、外部リソースとして配列データを持って、読み込むプログラムを書いた方が容量が少なくなる場合があります。
■if()else()は使わない。
if-else文はif-if文に置き換えて使います。そうした方が容量が軽くなります。
■switchとforはなるべく使わない。
switchはifで書いた方が軽くなります。
forは大きな配列を使う場合には仕方がないですが、ifを使った方が軽い場合がほとんどです。
ただし、可読性が大幅に損なわれるために、
■try{}catch{}を一つにまとめる。
例外処理を受け取るコードはtryをネストせずに1つだけ定義し、その中に詰め込みます。
エラーメッセージはtoString()を使って、取り出すといいでしょう。
(どうせ実機で例外処理が出る時は止まる時です)
■比較は0と行う。
0との比較はバイトコードで専用の命令が用意されているために、早くなり、サイズも軽くなります。
例えば、
for (int i=0;i<256;i++)
 array[i] = i;
という文は
for (int i=255;i>0;i--)
 array[i] = i;
と書いた方が良いです。

クラス特有の軽量化・高速化の手法です。クラスはなるべく使わないに限ります。
Java クラス&メソッド コーディング編
■Threadクラスは最低限しか使わない
startAppで呼ばれるプロセス以外に、スレッドは極力作成しないようにします。
ゲームの場合はメインループを走らせるスレッドの最低1つだけ作成します。
作る場合は、スレッド間の割り込みによる変数の書き換えなどにも考慮した設計にならざるおえないので、どうしてもコーディングが増えてしまいますし、
Runableを継承したクラスが肥大化するので、あまりオススメはしません。
■Timer,TimerTaskクラスは使わない。
理由はThreadクラスと一緒です。
もし、定時的にイベントをおこしたいのであれば、ThreadクラスとSystem.currentTimeMillis()メソッド,Thread.sleep()メソッドを使った方が、よりサイズが小さく、速くなります。
■Vector,Hashtableクラスは使わない。
とても便利なクラス群ですが、サイズが大きくなり、遅くなるので使いません。
どうせ携帯アプリで使うのは、変数オブジェクト(intやbyte)のVectorやHashtableである場合がほとんどなので、
Vectorは配列とSystem.arraycopy()メソッドを使って代用します。
Hashtableも、自前でput()メソッドとget()メソッドを実装します。
■リソース読み込みや破棄をした後にガベージコレクタを使用する。
メソッド内でメモリを大量に使った後などは、System.gc()メソッドを利用することで、空きメモリを最大限活用することができます。
例えば、画像を破棄した後や、大きな配列を使った後などに使用すると良いでしょう。
これは軽量化・高速化するわけでなく、逆にサイズも大きくなり、処理も遅くなりますが、適度な空き時間を使ってガベージコレクタをすることは、MemoryOutofErrorを起こしにくくさせます。
(いくらSystem.gc()をしたからといって、一度に大量の配列などを作るとヒープ不足は発生します。常にヒープ容量をRuntime.freeMemory()で確認して、制限内に収めるようにしてください)

発想の転換をして、容量の削減・高速化を図ろうというテクニカルな方法を載せておきます。
テクニック編
■ネットワークからリソースをダウンロードし、レコードストアに保存する。
容量を最大限に活用するために、jarにおさまりきらない部分は、ネットワーク(HTTP)からデータをダウンロードし、レコードストアに保存する方法を取ります。
P4型ならJARを80KByteめいいっぱい収めたとして、レコードストア20KByteまでリソースを保存できます。
P5,6型ならJARを200KByteめいいっぱい収めたとして、レコードストア56KByteまでリソースを保存できます。
ダウンロードと保存用のコードを書く手間が増えますが、この処理は初回起動時だけでいいので、有効な手段だと思います。
■バックスクリーンを持って、ちらつき無しの高速描画。
携帯アプリを作る際に悩みの種の1つに描画問題があります。基本的に直にビデオメモリにアクセスできないために、描画関数はとても遅いものとなってしまいます。
そこで、あらかじめスクリーンと同じ大きさのバックスクリーンImageを生成し、Canvas.paint(Graphics g)が呼ばれた時に、バックスクリーンをそのまま転送します。こうすることで、ちらつきの無く比較的高速なスクリーンの描画が可能になります。
欠点としては、バックスクリーン確保のためにヒープ容量を使ってしまうことです。(例えば240x256サイズで色深度1pixel=4byteなら246KB消費します)
■PNG画像を結合させて、プログラム内部で分割する。
リソースが少なくなればなるほど、ZIPアーカイブされたjar内のデータは少なくなります。
あらかじめPNG画像を結合させて、プログラム内で展開するようにすれば、処理はそこそこ重くなるけれど、ファイルサイズは大幅に減らすことができます。
1枚絵のままだと不便なら、新しく指定のサイズでImage.createImage(x,y)し、そのImageに結合画像から抽出します。
1枚絵のまま使用するのなら、描画にはあらかじめクリッピング領域(clipRect,setClip)を定義してdrawImageをします。(これはメソッドを作ると楽です。こちらの方がヒープ節約になります)
(端末上では展開できる画像サイズに上限があります。限度内に抑えるようにしてください)
■PNG画像のパレット書き換えで色違いの画像を動的に生成する。
同じ画像で色違い(ex.スライムとスライムベス)を作りたいのなら、PNGのパレットを書き換え方法で、リソースの大幅な節約ができます。
詳しくは、InQuestOfPowerの作者のもぶえさんのHPにサンプルが載っていますので、そちらを参考にしてみてください。

>>back to top