祝! 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に標準で組み込まれているもの)が有効になります。こんな感じです。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 -XX:+DTraceMethodProbes Test
>
">"がプロンプトです。ここにtest1やtest2と入力すると、次のように表示されます。>
>test1
0123456789
>test2
0123456789
>
Testを実行しているターミナルはそのままにして、以下の操作は新しいターミナルで行います。0123456789
>test2
0123456789
>
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というファイルに保存します。/copyinstr(arg1)=="Test"/
{
ts[copyinstr(arg3)]=timestamp;
}
hotspot$1:::method-return
/copyinstr(arg1)=="Test"/
{
printf("%s [%dns]\n",copyinstr(arg3),timestamp-ts[copyinstr(arg3)]);
}
プローブ
最初の行の「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のスクリプトを起動します。4659 Test
4774 Jps
4462
# 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分