「なんとなく速そう」ではなく「数字で語る」がパフォーマンス測定
パフォーマンス測定の一番大事なポイントは、
「体感」や「勘」ではなく、数字で語ることです。
「この実装の方が速い気がする」ではなく、
「この処理は平均 3.2ms、あっちは 7.8ms」と言えるようになる。
そのために必要なのは、難しいツールよりもまず、
「何を測るか」「どう測るか」「どう解釈するか」を押さえることです。
ここでは、初心者が最初に身につけるべきパフォーマンス測定の考え方と、
Java での具体的な測り方を、順を追ってかみ砕いていきます。
まず「何を測るのか」をはっきりさせる
処理時間なのか、スループットなのか、メモリなのか
パフォーマンスと一口に言っても、指しているものはいくつかあります。
ある一回の処理にどれくらい時間がかかるか(レイテンシ)。
単位時間あたりに何件処理できるか(スループット)。
どれくらいメモリを使うか(メモリフットプリント)。
例えば、「このメソッドのパフォーマンスを測りたい」と思ったとき、
本当に知りたいのは「1 回の呼び出しにかかる時間」なのか、
「1 秒間に何回呼べるか」なのか、
「呼び出し中にどれくらいメモリを消費するか」なのか。
ここを言葉にしておくと、測り方も解釈もブレなくなります。
例:ソート処理のパフォーマンスを測りたい場合
例えば、配列のソート処理のパフォーマンスを測るとします。
「1 回のソートに何ミリ秒かかるか」を知りたいのか。
「1 秒間に何回ソートできるか」を知りたいのか。
「ソート中にどれくらいメモリを使うか」を知りたいのか。
最初は「1 回の処理時間」を測るところから始めるのが分かりやすいです。
そこから必要に応じて、スループットやメモリに広げていけば十分です。
一番シンプルな測り方:System.nanoTime で時間を測る
基本パターン
Java で処理時間を測る一番シンプルな方法は、System.nanoTime() で前後の時間を取って差を出すことです。
public class SimpleMeasure {
public static void main(String[] args) {
long start = System.nanoTime();
doWork(); // 測りたい処理
long end = System.nanoTime();
long diff = end - start;
System.out.println("処理時間: " + diff + " ns");
}
static void doWork() {
int sum = 0;
for (int i = 0; i < 1_000_000; i++) {
sum += i;
}
}
}
JavananoTime() は「現在時刻」ではなく「単調増加するタイマー」なので、
差分を取る用途に向いています。
1 回だけ測るのは危険
ただし、1 回だけ測って「この処理は 3ms」と決めつけるのは危険です。
JIT コンパイルの影響(最初の数回は遅い)。
GC がたまたま走った。
OS のスケジューリングで一瞬止められた。
など、外乱要因がたくさんあるからです。
最低限、同じ処理を何度も繰り返して、
平均値や分布を見るようにしましょう。
ウォームアップと複数回計測の重要性
JIT コンパイルという「エンジンが温まる時間」
JVM は、最初から全力では動きません。
最初はバイトコードをインタプリタ的に実行し、
「よく呼ばれているメソッド」を見つけると、
JIT コンパイルでネイティブコードに変換して高速化します。
つまり、同じメソッドでも、
最初の数回は遅い。
何十回か呼ばれたあとから本気を出す。
という挙動になります。
これを無視して最初の 1 回だけ測ると、
「本来より遅い数字」をパフォーマンスだと思い込んでしまいます。
ウォームアップしてから測る
シンプルなやり方としては、
測定前に、同じ処理を何十回か実行して「ウォームアップ」する。
そのあとで、改めて何回か測定して平均を取る。
という流れにします。
public class WarmupMeasure {
public static void main(String[] args) {
// ウォームアップ
for (int i = 0; i < 10_000; i++) {
doWork();
}
// 測定
int runs = 10;
long total = 0;
for (int i = 0; i < runs; i++) {
long start = System.nanoTime();
doWork();
long end = System.nanoTime();
total += (end - start);
}
System.out.println("平均: " + (total / runs) + " ns");
}
static void doWork() {
int sum = 0;
for (int i = 0; i < 1_000_000; i++) {
sum += i;
}
}
}
Javaこれだけでも、「たまたま遅かった 1 回」に振り回されるリスクはかなり減ります。
マイクロベンチマークには JMH を使う、という発想
手書き計測の限界
ここまでの nanoTime を使った測定は、
「考え方を理解する」には十分ですが、
本格的なマイクロベンチマークには向きません。
JIT の最適化。
デッドコードの削除。
ループの巻き上げ。
GC の影響。
などを正しく扱うのは、手書きだとかなり難しいからです。
JMH という専用ツール
Java には、マイクロベンチマーク専用のフレームワークとして
JMH(Java Microbenchmark Harness)があります。
JMH を使うと、
ウォームアップ回数や測定回数を自動で管理してくれる。
スレッド数や測定モード(スループット、平均時間など)を指定できる。
JIT の最適化に潰されにくい形でコードを実行してくれる。
といったメリットがあります。
最初から JMH を完璧に使いこなす必要はありませんが、
「真面目にメソッド単位の速さを比べたい」と思ったら、
「JMH を使う」という選択肢を持っておくといいです。
アプリ全体のパフォーマンス測定:ログとメトリクス
「どこが遅いのか」を見える化する
メソッド単位のマイクロベンチマークとは別に、
アプリ全体として「どこで時間がかかっているのか」を知ることも重要です。
例えば Web アプリなら、
リクエストの受付からレスポンスまでに何ミリ秒かかっているか。
その中で、DB アクセスに何ミリ秒、外部 API に何ミリ秒かかっているか。
といった情報をログやメトリクスとして記録しておくと、
「遅いのはアプリのロジックなのか、DB なのか、ネットワークなのか」が見えてきます。
簡単な計測ログの例
例えば、ある処理の前後で時間を測ってログに出すだけでも、
かなりのヒントになります。
long start = System.currentTimeMillis();
service.doSomething();
long end = System.currentTimeMillis();
logger.info("doSomething took {} ms", (end - start));
Javaこれを重要なポイントに仕込んでおくと、
本番環境で「たまに遅い」と言われたときに、
どの処理がボトルネックになっているかを推測しやすくなります。
本格的には、
Micrometer や Prometheus などのメトリクス基盤と連携して、
ダッシュボードで可視化する世界に進んでいきますが、
最初の一歩は「時間を測ってログに出す」で十分です。
パフォーマンス測定でやりがちな落とし穴
落とし穴1:デバッグ用の println が測定結果を歪める
測定中に System.out.println を多用すると、
その I/O コストが支配的になってしまい、
本来測りたい処理の時間が見えなくなります。
測定するときは、
「ログ出力は最小限にする」
「結果だけ最後にまとめて出す」
といった工夫をしましょう。
落とし穴2:本番と違いすぎる環境で測る
ローカル PC のデバッグ実行と、
本番サーバー上の実行では、
CPU の性能。
コア数。
メモリ量。
GC の設定。
などが全く違います。
ローカルでの測定は「傾向を見る」には役立ちますが、
最終的な判断は、本番に近い環境での測定結果を基準にするべきです。
落とし穴3:平均値だけを見て安心する
平均値だけを見ると、
「たまにだけ極端に遅い」ケースを見逃します。
例えば、
平均 10ms だけど、1% のリクエストが 1 秒かかっている。
という状況は、ユーザー体験としてはかなり厳しいです。
可能であれば、
パーセンタイル(p95, p99 など)も一緒に見ると、
「たまにだけ遅い」問題に気づきやすくなります。
まとめ:パフォーマンス測定を自分の言葉で説明するなら
あなたの言葉で整理すると、こうなります。
「パフォーマンス測定は、『なんとなく速そう』ではなく『数字で語る』ための作業。
まず『何を測りたいのか』(処理時間か、スループットか、メモリか)をはっきりさせる。
単純な処理時間なら、System.nanoTime で前後を測り、ウォームアップしてから複数回実行して平均を取る。
本気でメソッド単位の速さを比べたいなら JMH を使う。
アプリ全体では、重要な処理の前後で時間を測ってログやメトリクスに残し、
『どこで時間がかかっているのか』を見える化する。
平均値だけでなく、たまにだけ遅いケースにも目を向ける。
大事なのは、
『測り方』と『解釈の仕方』をセットで考え、
数字をもとに冷静にボトルネックを特定していく姿勢。」
