0Pricing
Java Academy · Lekcja

Unikanie eliminacji martwego kodu

Blackhole i stan

Unikanie eliminacji martwego kodu to bezpłatna lekcja Java Academy na CoddyKit. To lekcja 4 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej Java Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Java Academy zawiera 4 lekcji w sumie.

Zapobieganie eliminacji martwego kodu

Największą pułapką mikrobenchmarków jest eliminacja martwego kodu (DCE): jeśli JIT dowiedzie, że wynik nie jest nigdy używany, usuwa obliczenia. Benchmark mierzy wtedy nic. JMH udostępnia dwa narzędzia, które temu zapobiegają — zwracanie wartości i obiekt Blackhole.

Pułapka DCE

Ta pętla oblicza pierwiastek kwadratowy milion razy, ale nigdy nie używa wyniku. Inteligentny kompilator może usunąć całą pętlę.

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));
    }
}

Rozwiązanie 1: Zwracanie wyniku

Najprostszą metodą ochrony jest zwracanie obliczonej wartości z metody @Benchmark. JMH wykorzystuje każdą zwróconą wartość, więc JVM nie może dowieść, że wynik jest martwy.

import org.openjdk.jmh.annotations.Benchmark;

public class Bench {
    @Benchmark
    public double sqrt() {
        return Math.sqrt(42.0); // returned -> consumed by JMH
    }
}

Rozwiązanie 2: Blackhole

Gdy benchmark generuje wiele wartości lub nie można zwrócić jednej z nich, należy wstrzyknąć obiekt Blackhole i wywołać consume(...). Blackhole przekonuje JIT, że każda wartość jest używana, bez kosztu rzeczywistych operacji.

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));
        }
    }
}

Obiekty @State

Klasa @State przechowuje dane wejściowe, które są nieprzejrzyste dla optymalizatora, zapobiegając constant folding. JMH tworzy jej instancję i przekazuje ją do benchmarku. Zakresem może być Thread, Benchmark (współdzielony) lub 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 i @TearDown

Wewnątrz klasy @State metody oznaczone adnotacją @Setup są wykonywane przed pomiarem, a metody z adnotacją @TearDown — po nim. Element Level określa częstotliwość: Trial (raz), Iteration lub 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;
    }
}

Unikaj stałych danych wejściowych

Nigdy nie przekazuj literałów jako danych wejściowych do testowanej metody. Math.sqrt(2.0) może zostać zwinięte do stałej; Math.sqrt(state.value) nie może. Zawsze pobieraj dane wejściowe z pola @State.

Zwracanie wielu wartości

Jeśli benchmark w naturalny sposób generuje dwa wyniki, mogą Państwo zwrócić jeden, a drugi przekazać do Blackhole albo połączyć je w jeden wynik. Pozostawienie któregoś z nich bez wykorzystania ponownie otwiera lukę umożliwiającą 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;
    }
}

Narzut Blackhole

Blackhole zaprojektowano tak, aby jego użycie było niezwykle tanie, a on sam odporny na optymalizacje, ale nie jest całkowicie bezkosztowy. W przypadku bardzo szybkich operacji należy preferować zwracanie pojedynczej wartości, a Blackhole rezerwować do obsługi pętli i wielu wartości.

Sprawdź wiarygodność wyników

Jeśli benchmark raportuje niewiarygodnie krótki czas (poniżej nanosekundy dla rzeczywistej pracy), należy podejrzewać DCE lub zwijanie stałych. Proszę ponownie sprawdzić, czy wyniki są zwracane lub przekazywane do konsumpcji oraz czy dane wejściowe pochodzą z @State.

Zastosowanie wszystkiego w praktyce

Solidny benchmark odczytuje dane wejściowe z obiektu @State, wykonuje pracę, a następnie albo zwraca wynik, albo przekazuje go do Blackhole.consume. Dzięki tym trzem nawykom DCE i zwijanie stałych nie mogą zafałszować pomiarów.

Szybki test

Proszę sprawdzić, czy rozumieją Państwo, jak zapobiegać eliminacji martwego kodu.

Podsumowanie

Nauczyli się Państwo, jak utrzymywać wiarygodność benchmarków:

  • Eliminacja martwego kodu usuwa niewykorzystywane obliczenia.
  • Należy zwracać pojedynczy wynik, aby JMH mógł go skonsumować.
  • Proszę używać Blackhole.consume w przypadku pętli i wielu wartości.
  • Obiekty @State ukrywają dane wejściowe, uniemożliwiając zwijanie stałych.
  • @Setup / @TearDown przygotowują stan i sprzątają po nim na wybranym poziomie Level.

Często zadawane pytania

Czy lekcja „Unikanie eliminacji martwego kodu” jest bezpłatna?

Tak — pełny tekst „Unikanie eliminacji martwego kodu” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu Java Academy, przejdź na CoddyKit PRO. Kurs Java Academy zawiera 4 lekcji w sumie.

Co nauczysz się w „Unikanie eliminacji martwego kodu”?

Blackhole i stan Ćwiczysz Java Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć Java Academy?

Nie wymagamy żadnego doświadczenia. Java Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 4 z 4.

Ile czasu zajmuje lekcja „Unikanie eliminacji martwego kodu”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji Java Academy?

Tak. Każda lekcja Java Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. Dlaczego JMH
  2. Pisanie benchmarku
  3. Rozgrzewka i iteracje
  4. Unikanie eliminacji martwego kodu
← Powrót do Java Academy