竹形誠司 ブログ
Java+MySQL+Tomcat    »トピック一覧
掲示板へのスパムが多いため、「ご質問」のコーナーはユーザー登録制とさせていただきました。お手数ですが、上の「新規ユーザーの登録」メニューより登録をお願いします。
帳票Web
アプリケーション

受注開発始めました
詳しくは こちら
竹形 誠司 著/ラトルズ刊
JSP帳票アプリケーション実践開発入門
JSP帳票アプリケーション
実践開発入門

JSP業務アプリケーション短期開発入門
JSP業務アプリケーション
短期開発入門

Java+MySQL+Tomcatで始めるWebアプリケーション構築入門
Java+MySQL+Tomcatで始めるWebアプリケーション構築入門

Java+MySQL+Tomcatで作る掲示板とブログ
Java+MySQL+Tomcatで作る
掲示板とブログ
DTrace で Javaのメソッドの実行時間を測る
by 竹形 誠司[takegata]
祝! Mac OSX版のJ2SE6.0リリース
私は最近知ったのですが、MacOS 10.5 (Leopard)のカーネルにはSunのデバッグ技術であるDTrace(ダイナミックトレース)が実装されているのだそうです。でも、JavaがDTraceに標準で対応するのは、J2SE6.0からなんですよ、残念!、と思っていたところMac OSX版J2SE6.0リリースの朗報。早速ためしてみました。

J2SE6.0のインストール
J2SE6.0はMacOSXのソフトウェア・アップデートで自動的に入るみたいなんですが、java -version を実行しても古いバージョンが表示されるので、わざわざhttp://developer.apple.com/java/ からダウンロードしてインストーラから入れてみたのですがやっぱり変わりません。

いろいろ調べてやっと分かったのは、[アプリケーション]->[ユーティリティ]->[java]->[Java Preferences]を起動して[Javaアプリケーション実行時の設定]でJava SE 6 の優先順位を1番にしないといけないということです。ただ、それでもルートシェルでjava -versionを実行すると、java version "1.5.0_13" と表示されます。なんでそんなことになるのか、いずれは調べようと思います。

テスト用のJavaプログラム
次のコードは、1行入力用のプロンプトを表示し、whileループの中で"test1" が入力されたらtest1()メソッドを呼び出し、"test2" が入力されたらtest2()メソッドを呼び出しています。どちらのメソッドが速いか比べてみるのが目的です。test1は文字列と文字列を+演算子で足しています。test2はStringBufferクラスのappendメソッドを使っています。
import java.io.*;
public class Test{
  public static void main(String[] args){
    try{
      BufferedReader br =
        new BufferedReader(new InputStreamReader(System.in));
      System.out.print(">");
      String strLine;
      while(!(strLine=br.readLine()).equals("exit")){
        String[] token = strLine.split(" ");
        String strCommand = token[0];
        if(strCommand.equals("test1")){
          test1();
        }else if(strCommand.equals("test2")){
          test2();
        }else{
          System.out.println("no such command.");
        }
        System.out.print(">");
    }
    }catch(IOException e){
    e.printStackTrace();
    }
  }

  static void test1(){
    String str="";
    for(int i=0;i<10;i++){
      str+=i;
    }
    System.out.println(str);
  }

  static void test2(){
    StringBuffer sb = new StringBuffer();
    for(int i=0;i<10;i++){
      sb.append(i);
  }
    System.out.println(sb.toString());
  }

}
Javaのプログラムには、特にデバッグ用のコードをなど埋め込む必要がありません。これがDTraceのいいところですね。javaの起動オプションで -XX:DTraceMethodProbes を指定すると、メソッド呼び出し時に発火する(DTraceではイベントが発生することを「発火(fire)」と呼ぶようです)プローブ(J2SE6.0に標準で組み込まれているもの)が有効になります。こんな感じです。
$ java -XX:+DTraceMethodProbes Test
>
">"がプロンプトです。ここにtest1やtest2と入力すると、次のように表示されます。
>test1
0123456789
>test2
0123456789
>
Testを実行しているターミナルはそのままにして、以下の操作は新しいターミナルで行います。

DTraceのスクリプト
DTraceのスクリプトはDという言語で書きます。こんな感じです。
hotspot$1:::method-entry
/copyinstr(arg1)=="Test"/
{
  ts[copyinstr(arg3)]=timestamp;
}

hotspot$1:::method-return
/copyinstr(arg1)=="Test"/
{
  printf("%s [%dns]\n",copyinstr(arg3),timestamp-ts[copyinstr(arg3)]);
}
ここでは、これをtest.dというファイルに保存します。

プローブ
最初の行の「hotspot$1:::method-entry」はプローブの指定です。プローブ指定の行はコロンで次の4つの区域に区切られています。

(1)プロバイダ:プローブの提供者です。hotspotはjavaの実行環境で、$1にはプロセスIDを代入します。プロセスIDは起動時に引数で指定します。
(2)モジュール:プローブが属しているカーネルモジュールやユーザライブラリを指定します。指定しなければ全てのモジュールが対象になります。
(3)関数:プローブが属している関数を指定します。指定しなければ全てのモジュールが対象になります。
(4)プローブ名:プローブの名前を指定します。

モジュールと関数はC言語の世界の話なので、Javaではあまり使いません(使う人もいるかも知れませんが、素人にはわけのわからない文字列に見えます)。method-entryはメソッドが呼び出された時、method-returnはメソッドがリターンする時に発火するプローブです(J2SE6.0であらかじめ定義されているもの)。

述語
2行目の前後をスラッシュ(/)で囲んだ行は「述語(Predicate)」と呼ばれるもので、ここには続くカッコ内の処理を実行する条件を書きます。上の場合は、プローブの引数を条件に使っています。method-entryプローブの引数は次のようになっています。

arg0:メソッドに入る、またはメソッドを出るスレッドの Java スレッド ID
arg1:メソッドのクラスの名前を含む UTF-8 文字列データへのポインタ
arg2:クラス名データの長さ (バイト単位)
arg3:メソッドの名前を含む UTF-8 文字列データへのポインタ
arg4:メソッド名データの長さ (バイト単位)
arg5:メソッドの署名を含む UTF-8 文字列データへのポインタ
arg6:署名データの長さ (バイト単位)

/copyinstr(arg1)=="Test"/ は、呼び出されたメソッドのクラスがTestのときだけ処理を実行する述語になります。

連想配列
ts[copyinstr(arg3)]=timestamp; の部分では、メソッドが呼び出された時の時刻を配列に記録しています。DTraceの配列は「連想配列(associative array)」と呼ばれるタイプで、キーには整数だけでなく文字列を指定することができます。ハッシュテーブルのようなものですね。

timestampはマシンの時刻をナノ秒単位で返す組み込み変数です。この行では、test1()メソッドが呼び出されたときの時刻をts["test1"] に、test2()メソッドが呼び出されたときの時刻をts["test2"]に格納します。

実行時間の計測
メソッドの処理が終わってリターンする際に、method-return というプローブが発火します。このプローブに対する処理の中では、「現在の時刻」と「メソッドが呼び出された時刻」の差分を計算し、メソッド名とともに表示しています。printf関数の使い方はCのprintfとほぼ同じです。

DTraceの実行
まず、JavaVMを実行しているプロセスのIDを調べる必要があります。これはJ2SEに付属のjpsというツールで調べられます。
$ jps
4659 Test
4774 Jps
4462
実行中のTestのプロセスIDが 4659 であることが分かります。DTraceを実行するには特権が必要なので、ここでルートシェルを起動し、次のコマンドでDTraceのスクリプトを起動します。
# dtrace -qs test.d 4659
オプション q は、画面に表示する情報を「明示されたものだけ」に制限するもの、オプション s はスクリプトファイルを指定するためのものです。

Testを実行しているターミナルで"test1"と入力すると、test1()メソッドが呼び出され、DTraceを実行しているターミナルに次のように表示されます。
test1 [1389749ns]
"test2" と入力すると、次のような感じになります。
test2 [900084ns]
この結果からtest2()の方がtest1()より処理時間が短いことが分かります。測定値は状況によって変わることがあるので、交互に複数回計測して比較する必要がありそうです。

Javaコードの中にテストコードを埋め込めば同様の計測も可能ですが、DTraceを使った場合は「Javaコードを修正する必要がない」というのがポイントですね。

参考:
DTrace Topics Java -Siwiki-
http://www.solarisinternals.com/wiki/index.php/DTrace_Topics_Java
HotSpot VM 内の DTrace プローブ
http://java.sun.com/javase/ja/6/docs/ja/technotes/guides/vm/dtrace.html
投稿:竹形 誠司[takegata]/2008年 05月 09日 23時 11分 /更新:2011年 04月 21日 00時 40分
Tomcatの場合
by 竹形 誠司[takegata]
Tomcatの場合は
export CATALINA_OPTS="-XX:+DTraceMethodProbes"
をやっておいて
%CATALINA_HOME%/bin/start.sh
を実行するとプローブが有効になります。

あと、クラスがパッケージに含まれている場合は、パッケージの階層の区切りはドット(.)ではなくスラッシュ(/)で指定する必要があります。
投稿:竹形 誠司[takegata]/2008年 05月 10日 02時 04分 /更新:2008年 05月 10日 02時 05分