JDK 1.1 開発ガイド (Solaris 編)

第 5 章 アプリケーションのパフォーマンスチューニング

この章では、Solaris 8 環境において Java アプリケーションのパフォーマンスを向上させる方法について説明します。アプリケーションのパフォーマンスとは、そのアプリケーションの資源の使用量と定義することができます。パフォーマンスチューニングとは、資源の使用量を最低限に抑えることです。


注意 - 注意 -

ここで紹介するパフォーマンスチューニングに関するヒントの多くは、Solaris 2.6、Solaris 7、および Solaris 8 で使用する Java に固有です。将来のリリースでは、パフォーマンス特性が変わることが予想され、ここで紹介しているヒントが適切でなくなる可能性があります。


チューニングに関するヒント

以降で説明するように、チューニングはいくつかのレベルで行うことができます。

システムインタフェース

チューニングによって大幅なパフォーマンスの向上が見込める Java システムのインタフェースには、次のものがあります。

コンパイラによる最適化

以下のコンパイラによる最適化が可能です。

コードのチューニング

パフォーマンス向上のためには、以下の部分のコードをチューニングします。

入出力

一般的に、Java アプリケーションで最も一般的で大きなパフォーマンス上の問題は、非効率的な入出力です。このため、一般的に入出力の問題は、Java アプリケーションのパフォーマンスチューニングで最初に検討すべき問題になります。入出力の問題を解決することによって、その他のすべての最適化を行なった場合よりも大幅にパフォーマンスが向上することがあります。効率的な入出力手法を用いることによって、10 倍以上の速度の向上が得られることも珍しくありません。

アプリケーションが大量の入出力を行う場合は、入出力のパフォーマンスチューニングを行なってみてください。チューニング結果は、アプリケーションをプロファイルすることによって確認できます。Java アプリケーションのプロファイルするには、Sun の Java WorkShopTM 製品を使用することができます。Java WorkShop は、以下の URL から入手することができます。

Java WorkShop のオンラインヘルプで、プロファイラまたはプロファイルについての説明を参照してください。以下の例は、4 つの異なるメソッドを使用して、150,000 行からなるファイルを読み取るベンチマークテストの結果です。

  1. DataInputStream.readLine() のみ (バッファーなし)

  2. DataInputStream.readLine()BufferedInputStream (2048 バイトのバッファーあり)

  3. BufferedReader.readline() (8192 バイトのバッファーあり)

  4. BufferedFileReader(fileName)

結果は次のとおりです (単位: 秒)。

 DataInputStream: 178.740
 DataInputStream(BufferedInputStream): 21.559
 BufferedReader 11.150
 BufferedFileReader  6.991

メソッド 1 と 2 では Unicode の文字が正しく処理されませんが、メソッド 3 と 4 では Unicode 文字が正しく処理されることに注意してください。つまりほとんどの製品では、メソッド 1 と 2 は使用できないことになります。JDK 1.1 では、DataInputStream.readLine() も推奨されません。Java WorkShop とその他のプログラムでは、メソッド 1 が使用されています。

Solaris の入出力処理の問題を見つけるためのもう 1 つの方法として、truss(1) を使用して、read(1)write(1) システムコールを検索するという方法もあります。

文字列

文字列に関して忘れてならない最重要事項は、ループで文字を処理するときには、StringStringBuffer クラスではなく必ず char 配列を使用することです。配列要素へアクセスする方が、charAt() メソッドを使用して文字列内の文字にアクセスするよりもはるかに高速です。また、文字列定数 ("...") はすでに文字列オブジェクトであることを忘れないでください。

//DON'T

String s = new String("hello");

//DO

String s = "hello";

配列

配列は境界が検査されるので、その分パフォーマンスが低下します。ただし、配列へのアクセスは、ベクトルや StringStringBuffer にアクセスするよりははるかに高速です。より高いパフォーマンスを得るには、System.arraycopy() を使用してください。これはネイティブメソッドであり、手動の配列処理よりもかなり高速です。

ベクトル

Vector は便利ですが非効率的です。最高のパフォーマンスを得るには、構造体のサイズが不明で効率性がそれほど重要でない場合にのみ使用してください。Vector を使用する場合は、パフォーマンスが低下するのでループ内で elementAt() を使用しないでください。Vector は、次の特徴を持つ配列に対してのみ使用してください。

ハッシュ

HashTable には、以下のチューニング可能なパラメータがあります。

イメージ

イメージについては、次のような方法があります。

塗りつぶしと描画

塗りつぶしと描画のパフォーマンスを向上させるには、次の方法を使用してください。

非同期の読み込み

非同期の読み込みパフォーマンスを向上させるには、独自の imageUpdate() メソッドを使用して imageUpdate() をオーバーライドします。imageUpdate() は、必要以上に再描画を行うことがあります。


//wait for the width information to be loaded
		while (image.getWidth(null) == -1 {    
   		try {         
      	Thread.sleep(200);       
			}
			catch(InterruptedException e) {
   		} 
		}  
		if (!haveWidth) {
			synchronized (im) {
      	if (im.getWidth(this) == -1) {          
					try {
            	im.wait();
         	}
        		catch (InterruptedException) {
         	}       
      	}    
   		}  
			//If we got this far, the width is loaded, we will never go thru 
			// all that checking again.     
			haveWidth = true; 
		} 
... 
public boolean imageUpdate(Image img, int flags, int x, int y, int width, int height) {
		boolean moreUpdatesNeeded = true;     
		if ((flags&ImageObserver.WIDTH)!= 0 {
   		synchronized (img) {
     		img.notifyAll();
				moreUpdatesNeeded = false;
			}
		}
		return 	
		moreUpdatesNeeded; 
	}    

事前のデコード

イメージのデコードは、読み込みより長い時間がかかります。PixelGrabberMemoryImageSource を使用して事前にデコードすることによって、複数のイメージを 1 つのファイルにまとめ、最高の速度が得ることができます。この方法は、ポーリングを行うよりも効率的です。

メモリー使用

アプリケーションのパフォーマンスは、実行中のガーベッジコレクション量を少なくすることによって大幅に向上させることができます。また、次の方法によってもパフォーマンスを向上できます。

スレッド

「Solaris 環境における従来の Java スレッド *」で説明したように、アリケーションのパフォーマンスは、ネイティブメソッドを使用することによって大幅に向上します。グリーンスレッドがタイムスライスされることはないため、実行状態を示すには、ループ内での Thread.yield() の呼び出しが必要になり、実行速度が低します。その他、次の方法は使用しないでください。

コンパイラによる最適化

Java コンパイラと JIT コンパイラは、次のような最適化を自動的に行います。

Java コンパイラ

JIT コンパイラ

コード最適化

ループ

パフォーマンスを向上させるには、次のことを守ってください。

真偽式のテーブルルックアップへの変換

値がある範囲の小さな整数である 1 つの式に基づいて値が選択される場合は、テーブルルックアップに変換してください。条件分岐があると、コンパイラによる最適化の多くが行われなくなります。

キャッシュ

キャッシュによってメモリーの使用量は増えますが、パフォーマンス向上に利用することができます。フェッチや計算に重い負荷がかかる値は、キャッシュを利用してください。

結果の事前計算

コンパイル時にわかっている値を事前に計算しておくと、パフォーマンスが向上します。

評価の引き延ばし

必要になるまで結果の計算を遅らせると、起動時間が短縮されます。

クラスとオブジェクトの初期化

1 回だけ行われる初期化をすべて 1 つのクラスイニシャライザでまとめて行うようにすると、パフォーマンスが向上します。