仕事でJavaプログラムのパフォーマンスも改善して欲しいという話になった。
プログラムのパフォーマンスを改善する場合、闇雲にチューニングするのではなく、まず現状のプログラムのどの辺が重たいのか調べなければならない。たった数行のコードが全実行時間のほとんどを占めている場合もあるからだ。
対象のシステムはjavaで作られており、1メソッド呼び出しで前回の呼び出しからの実行時間をログに吐き出すようなクラスを作っていたのだが、SystemクラスにnanoTime()という便利そうなメソッドがあることを今更知った。実行時間の取得は当たり前のようにcurrentTimeMillis()を使っていたので衝撃だった。winAPI経由だとどう頑張っても16~15ms以下の精度は出ないからだ[0]。昔、この問題で大学同期のdesutanが苦しみ、自分もCでアセンブラを組み込んでミリ秒以下の精度を出そうと頑張っていたのがウソみたい。
nano秒とか体感できネーヨと思うが、実際に使ってみると確かにcurrentTimeMillisよりは精度があるようだ。1,000,000 倍して[ms]に変換してみたが2msとか15ms以下の値が取得できた。調べてみるとwindowsの場合、μ秒オーダーらしい。[1]
時間取得そのものにかかるオーバヘッドに関してもcurrentTimeMillisよりnanoTimeの方が優秀みたい。今後はこっちを使うようにしよっと。IBMも勧めてるし[2]。
それにしても...
public static long nanoTime()ナノ秒単位でも63ビットあれば292年分を表現できるんだな...。約 292 年後の心配をしてくれるsunはエライ。おそらくこの中に含まれていない先頭1ビットが"将来的に、値が負の数になる可能性があります。"って部分と関係しているのだろう。利用可能でもっとも正確なシステムタイマーの現在の値をナノ秒単位で返します。
このメソッドは、経過時間を測定するためだけに使用できます。 システムのほかの概念や壁時計の時刻に関連していません。返される値は、固定された任意の時間からの経過時間 (ナノ秒) です。将来的に、値が負の数になる可能性があります。このメソッドは、ナノ秒単位の精度を提供しますが、必ずしもナノ秒の正確度ではありません。値の変更 頻度は保証されません。約 292 年 (263 ナノ秒 ) を超える連続した呼び出しの差異では、数値のオーバーフローにより経過時間が正しく計算されません。
参考
[0]Java/リフレクション/currentTimeMillisとnanoTimeを選択 - discypus
[1]時間を計測する
[2]確実な Java ベンチマーク: 第 1 回 問題