Perf-01: 処理時間測定(System.nanoTime)
Java で処理時間を正確に計測する方法を解説します。System.nanoTime() と System.currentTimeMillis() の違い、 JIT コンパイルの影響を受けないウォームアップ計測、 文字列結合 vs StringBuilder・ArrayList vs LinkedList の速度比較を学びましょう。
いつ使うか
- 処理が遅いと感じた箇所でボトルネックを特定したいとき
- アルゴリズムや実装方法を変えたときに改善効果を数値で確認したいとき
- バッチ処理の全体所要時間をログに記録したいとき
- ループ回数やデータ量を変えたときの処理時間の変化を測りたいとき
nanoTime vs currentTimeMillis の比較
| メソッド | 精度 | 用途 | 注意点 |
|---|---|---|---|
nanoTime() | ナノ秒 | 処理時間計測(相対) | JVM 内の相対値のみ有効。壁時計時間ではない |
currentTimeMillis() | ミリ秒 | 現在時刻の取得・記録 | OS の時刻同期の影響を受ける。短い処理計測には不向き |
サンプルコード
Java 8 では Runnable を匿名クラスで記述します。System.nanoTime() の戻り値はナノ秒(10億分の1秒)なので、1_000_000 で割るとミリ秒になります。
よくあるミス・注意点
nanoTime() の差は同一 JVM 内の比較にのみ使う
System.nanoTime() の戻り値は、 特定の基準点からの経過ナノ秒数です。この基準点は JVM ごとに異なるため、 異なる JVM やマシン間で値を比較することはできません。 同一プロセス内で「開始時刻 - 終了時刻」として差を計算する用途にのみ使用してください。
最初の数回はウォームアップとして計測から除外する
Java では JIT(Just-In-Time)コンパイラがプログラム実行中にコードを最適化します。 最初の数回の実行はまだ JIT 最適化が行われていないため、実際より遅い結果になります。 正確なベンチマークを行うには、計測前に同じ処理を数回「ウォームアップ」として実行しましょう。
マイクロベンチマークには JMH を使う
数マイクロ秒(μs)オーダーの細かい処理を計測する場合、JIT コンパイラ・GC(ガベージコレクション)・ OS のスケジューラが測定結果に大きく影響します。 本格的なベンチマークには JMH(Java Microbenchmark Harness)という専用ツールを使うことが推奨されています。
currentTimeMillis() は短い処理の計測に使えない
System.currentTimeMillis() はミリ秒精度ですが、 OS によっては 10〜15 ms 単位でしか値が更新されません。 1 ms 未満の処理を計測すると常に 0 ms になります。短い処理の計測には必ず nanoTime() を使いましょう。
テストする観点
measureNanoTimeが正の値(0 より大きい値)を返すこと- StringBuilder 版の処理時間が文字列結合(+ 演算子)より短いこと(大きな n で有意な差が出る)
ArrayList.get()の処理時間がLinkedList.get()より短いこと(ランダムアクセス比較)- ウォームアップあり計測の結果がウォームアップなしより安定すること(標準偏差が小さい)
- warmupCount=0 や measureCount=1 などの境界値でも例外が発生しないこと