ボトルネックの特定
最適化の前に測定します。
「ボトルネックの特定」はCoddyKit上の無料Java Academyレッスンです。 これはレッスン1/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはJava Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Java Academyコースには全4レッスンが含まれています。
推測せず測定する
パフォーマンス改善の第一原則は、最適化の前に測定することです。
Javaプログラムがどこで時間を使っているかについての直感は、たいてい外れます。JITコンパイラー、ガベージコレクター、キャッシュがあるため、根拠のない推測は通用しません。プロファイリングを行い、本当のホットスポットを見つけて、そこを修正してください。
ボトルネックとは
ボトルネックとは、システム全体のスループットまたはレイテンシを制限する部分です。
それ以外の部分を最適化しても、目に見える効果はありません。アムダールの法則はこれを明確に示します。たとえば、時間の90%が1つのメソッドに費やされている場合、残り10%を高速化しても、改善幅は11%を超えられません。
レイテンシとスループット
何を最適化するのかを決めてください。
- レイテンシ — 1つのリクエストにかかる時間です。
- スループット — 1秒あたりに処理できるリクエスト数です。
両者にはトレードオフがあります。バッチ処理はスループットを向上させますが、リクエストごとのレイテンシを増加させる可能性があります。チューニングする前に、目標を把握してください。
ウォールクロックによる計測
最も単純な測定方法は、System.nanoTime() を使ってコードブロックの前後の経過時間を測ることです。
簡単な妥当性確認には便利ですが、JITのウォームアップ、GCの一時停止、OSのスケジューリングによるノイズも含まれます。そのため、単一の数値は慎重に扱ってください。
public class Main {
public static void main(String[] args) {
long start = System.nanoTime();
long sum = 0;
for (int i = 0; i < 10_000_000; i++) sum += i;
long elapsed = System.nanoTime() - start;
System.out.println("Sum: " + sum);
System.out.println("Elapsed ms: " + (elapsed / 1_000_000.0));
}
}JITのウォームアップに注意する
Javaはまずバイトコードを解釈実行し、その後JITがホットなメソッドをネイティブコードにコンパイルします。
そのため、メソッドの最初の実行は、後の実行よりはるかに遅くなります。単純な計測ループでは、主にウォームアップを測ることになります。実際のベンチマークでは、まずウォームアップを行い、その後で定常状態を測定します。JMHはこの処理を自動で行ってくれます。
CPUバウンドとI/Oバウンド
ボトルネックの種類を判定してください。
- CPUバウンド — スレッドが計算で忙しく、CPUコアが使い切られている状態です。
- I/Oバウンド — スレッドがディスク、ネットワーク、またはデータベースを待っている状態です。
プロファイラーでは、これらを「on-CPU」時間と「blocked/waiting」時間として区別できます。対処方法はまったく異なります。高速なアルゴリズムを使うか、同時実行性を高めるか、往復回数を減らします。
サンプリングとインストルメンテーション
プロファイリングには、次の2つの方法があります。
- サンプリング — 一定間隔でスタックトレースを取得します。オーバーヘッドが小さく、統計的な方法です。
- インストルメンテーション — すべてのメソッドにカウンターを組み込みます。正確ですが負荷が大きく、計測時間を歪める可能性があります。
本番環境では、Java Flight Recorderのようなオーバーヘッドの小さいサンプリングを優先してください。
メモリがボトルネックになる場合
多くの場合、本当のコストは計算ではなく割り当てにあります。オブジェクトを過剰に生成するとガベージコレクションが頻繁に発生し、CPUを消費したり、一時停止を増やしたりします。
割り当て率とGC時間を確認してください。ホットループ内の割り当てを減らすほうが、算術処理を細かくチューニングするより効果的なことがよくあります。
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
// Allocation-heavy: a new String each iteration
List<String> garbage = new ArrayList<>();
for (int i = 0; i < 5; i++) {
garbage.add("item-" + i);
}
System.out.println("Allocated " + garbage.size() + " strings");
System.out.println("In a hot loop, this churn drives GC pressure");
}
}スタックの最上位を見つける
サンプリングプロファイラーは、CPUスタック上に現れた頻度によってメソッドを順位付けした一覧を生成します。これが自己時間です。
最上位のメソッドが候補になります。ただし、それがクリティカルパス上にあることを確認してください。バックグラウンドのロガーで実行されるホットメソッドは、ユーザー向けのレイテンシに影響しない可能性があります。
ベースラインを確立する
何かを変更する前に、現実的な負荷の下でベースラインの測定値を記録してください。
変更するたびに再測定して比較します。ベースラインがなければ、最適化によって改善したことを証明できません。多くの「最適化」は、かえって状況を悪化させます。一度に1つだけ変更してください。
現実的な負荷でプロファイリング
アイドル状態のノートPCで見つかったボトルネックが、本番環境で性能を損なうものとは限りません。
- 実際の利用状況を反映したデータサイズと同時実行数を使います。
- ユーザーにとって実際に重要なワークロードを再現します。
合成マイクロテストでは、規模が大きくなったときには無関係なメソッドに導かれることがあります。本当に問題が起きている箇所をプロファイリングしてください。
クイックチェック
Javaメソッドを1回だけ、単純に System.nanoTime() で計測すると、なぜ誤解を招くことが多いのでしょうか?
まとめ
規律正しくボトルネックを見つける方法:
- 最適化の前に計測します。直感は当てになりません。
- レイテンシーまたはスループットという目標を選びます。
- CPUバウンドかI/Oバウンドかを分類し、GCやアロケーションを監視します。
- オーバーヘッドの小さいサンプリングプロファイラを優先します。
- JITのウォームアップに注意し、ベースラインを確立して、一度に1つだけ変更します。
よくある質問
「ボトルネックの特定」レッスンは無料ですか?
はい。「ボトルネックの特定」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Java Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Java Academyコースには全4レッスンが含まれています。
「ボトルネックの特定」で何を学びますか?
最適化の前に測定します。 ブラウザで直接実行するハンズオンコードでJava Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
Java Academyを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのJava Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン1/4です。
「ボトルネックの特定」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このJava Academyレッスンでコードを書いて実行できますか?
はい。すべてのJava Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。