デッドコード削除の回避
Blackhole と状態を扱います。
「デッドコード削除の回避」はCoddyKit上の無料Java Academyレッスンです。 これはレッスン4/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはJava Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Java Academyコースには全4レッスンが含まれています。
デッドコード除去の回避
マイクロベンチマークで最大の落とし穴はデッドコード除去(DCE)です。JIT が結果をまったく使わないと判断すると、計算を削除してしまいます。その場合、ベンチマークは何も測定していません。JMH には、これを防ぐ 2 つの方法があります。値を返すことと、Blackhole を使うことです。
DCE の落とし穴
このループは平方根を 100 万回計算しますが、結果をまったく使っていません。賢いコンパイラなら、ループ全体を削除できます。
public class Main {
public static void main(String[] args) {
long t = System.nanoTime();
for (int i = 0; i < 1_000_000; i++) {
double ignored = Math.sqrt(i); // result thrown away
}
System.out.println("ns: " + (System.nanoTime() - t));
}
}修正 1: 結果を返す
最も簡単な対策は、@Benchmark メソッドから計算結果を返すことです。JMH は返されたすべての値を消費するため、JVM はその値が不要だと判断できません。
import org.openjdk.jmh.annotations.Benchmark;
public class Bench {
@Benchmark
public double sqrt() {
return Math.sqrt(42.0); // returned -> consumed by JMH
}
}修正 2: Blackhole
ベンチマークが複数の値を生成する場合や、1 つの値を返せない場合は、Blackhole を注入して consume(...) を呼び出します。Blackhole は、実際の処理にかかるコストなしに、各値が使われていると JIT に認識させます。
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.infra.Blackhole;
public class Bench {
@Benchmark
public void manyValues(Blackhole bh) {
for (int i = 0; i < 100; i++) {
bh.consume(Math.sqrt(i));
}
}
}@State オブジェクト
@State クラスは、オプティマイザーから不透明に見える入力を保持し、定数畳み込みを防ぎます。JMH がこのクラスをインスタンス化して、ベンチマークに渡します。スコープには Thread、Benchmark(共有)、Group を指定できます。
import org.openjdk.jmh.annotations.*;
public class Bench {
@State(Scope.Thread)
public static class Data {
public int x = 21;
}
@Benchmark
public int multiply(Data d) {
return d.x * 2; // d.x is not a compile-time constant
}
}@Setup と @TearDown
@State クラス内で、@Setup が付いたメソッドは測定前に実行され、@TearDown が付いたメソッドは測定後に実行されます。Level で実行頻度を制御します。指定できるのは Trial(1 回)、Iteration、Invocation です。
import org.openjdk.jmh.annotations.*;
@State(Scope.Thread)
public class Data {
int[] arr;
@Setup(Level.Trial)
public void init() {
arr = new int[1000];
for (int i = 0; i < arr.length; i++) arr[i] = i;
}
}定数入力を避ける
テスト対象メソッドにリテラル定数を決して渡さないでください。Math.sqrt(2.0)は定数に畳み込まれる可能性がありますが、Math.sqrt(state.value)は畳み込まれません。入力は必ず@Stateフィールドから取得してください。
複数の値を返す
ベンチマークで自然に2つの結果が生成される場合は、一方を返し、もう一方をBlackholeで消費するか、結果を組み合わせることができます。どちらか一方でも消費しないままにすると、DCEの抜け道が再び生じます。
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.infra.Blackhole;
public class Bench {
@Benchmark
public int twoResults(Blackhole bh) {
int a = 3 * 7;
int b = 5 + 9;
bh.consume(b);
return a;
}
}Blackholeのオーバーヘッド
Blackholeは非常に低コストで、Blackhole自体も最適化されにくいように設計されていますが、完全に無料ではありません。極めて高速な処理では単一の値を返す方法を優先し、ループや複数の値を扱う場合にBlackholeを使用してください。
数値の妥当性を確認する
ベンチマークが実際の処理としては不可能なほど短い時間(ナノ秒未満)を報告した場合は、DCEまたは定数畳み込みを疑ってください。結果を返すか消費していること、入力が@Stateから取得されていることを再確認してください。
総仕上げ
堅牢なベンチマークでは、@Stateオブジェクトから入力を読み取り、処理を実行したうえで、結果をreturnするかBlackhole.consumeで消費します。この3つの習慣により、DCEや定数畳み込みによって測定結果が歪められることを防げます。
クイックチェック
デッドコード除去を防ぐ方法についての理解度を確認してください。
振り返り
ベンチマークを正確に保つ方法を学びました。
- デッドコード除去は、使用されていない計算を削除します。
- JMHが消費できるように、単一の結果を返します。
- ループや複数の値にはBlackhole.consumeを使用します。
- @Stateオブジェクトによって入力を不透明に保ち、定数畳み込みを防ぎます。
- @Setup / @TearDownによって、指定したLevelで状態の準備と後片付けを行います。
よくある質問
「デッドコード削除の回避」レッスンは無料ですか?
はい。「デッドコード削除の回避」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Java Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Java Academyコースには全4レッスンが含まれています。
「デッドコード削除の回避」で何を学びますか?
Blackhole と状態を扱います。 ブラウザで直接実行するハンズオンコードでJava Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
Java Academyを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのJava Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン4/4です。
「デッドコード削除の回避」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このJava Academyレッスンでコードを書いて実行できますか?
はい。すべてのJava Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- JMH を使う理由
- ベンチマークの記述
- ウォームアップと反復
- デッドコード削除の回避