Микробенчмаркинг с Java

Микробенчмаркинг с Java

1. Вступление

Эта небольшая статья посвящена JMH (Java Microbenchmark Harness). Это было добавлено в JDK, начиная с JDK 12; для более ранних версий мы должны явно добавить зависимости в наши проекты.

Проще говоря, JMH заботится о таких вещах, как разогрев JVM и пути оптимизации кода, делая тестирование максимально простым.

2. Начиная

Для начала мы можем продолжить работу с Java 8 и просто определить зависимости:


    org.openjdk.jmh
    jmh-core
    1.19


    org.openjdk.jmh
    jmh-generator-annprocess
    1.19

Последние версииJMH Core иJMH Annotation Processor можно найти в Maven Central.

Затем создайте простой тест, используя аннотацию@Benchmark (в любом общедоступном классе):

@Benchmark
public void init() {
    // Do nothing
}

Затем мы добавляем основной класс, который запускает процесс бенчмаркинга:

public class BenchmarkRunner {
    public static void main(String[] args) throws Exception {
        org.openjdk.jmh.Main.main(args);
    }
}

Теперь запускBenchmarkRunner выполнит наш, возможно, несколько бесполезный тест. После завершения запуска представляется сводная таблица:

# Run complete. Total time: 00:06:45
Benchmark      Mode  Cnt Score            Error        Units
BenchMark.init thrpt 200 3099210741.962 ± 17510507.589 ops/s

3. Типы тестов

JMH поддерживает несколько возможных тестов:Throughput,AverageTime,SampleTime иSingleShotTime. Их можно настроить с помощью аннотации@BenchmarkMode:

@Benchmark
@BenchmarkMode(Mode.AverageTime)
public void init() {
    // Do nothing
}

Полученная таблица будет иметь среднюю метрику времени (вместо пропускной способности):

# Run complete. Total time: 00:00:40
Benchmark Mode Cnt  Score Error Units
BenchMark.init avgt 20 ≈ 10⁻⁹ s/op

4. Настройка разминки и выполнения

Используя аннотацию@Fork, мы можем настроить, как происходит выполнение теста: параметрvalue определяет, сколько раз будет выполняться тест, а параметрwarmup определяет, сколько раз тест будет выполняться. выполнит пробный прогон до сбора результатов, например:

@Benchmark
@Fork(value = 1, warmups = 2)
@BenchmarkMode(Mode.Throughput)
public void init() {
    // Do nothing
}

Это инструктирует JMH запустить две разминки и сбросить результаты, прежде чем переходить к тестированию в реальном времени.

Кроме того, аннотация@Warmup может использоваться для управления количеством итераций разминки. Например,@Warmup(iterations = 5) сообщает JMH, что будет достаточно пяти итераций разминки вместо 20 по умолчанию.

5. государственный

Теперь давайте посмотрим, как можно выполнить менее тривиальную и более показательную задачу тестирования алгоритма хеширования, используяState. Предположим, мы решили добавить дополнительную защиту от словарных атак на базу паролей, хешируя пароль несколько сотен раз.

Мы можем исследовать влияние на производительность, используя объектState:

@State(Scope.Benchmark)
public class ExecutionPlan {

    @Param({ "100", "200", "300", "500", "1000" })
    public int iterations;

    public Hasher murmur3;

    public String password = "4v3rys3kur3p455w0rd";

    @Setup(Level.Invocation)
    public void setUp() {
        murmur3 = Hashing.murmur3_128().newHasher();
    }
}

Наш метод сравнения будет выглядеть так:

@Fork(value = 1, warmups = 1)
@Benchmark
@BenchmarkMode(Mode.Throughput)
public void benchMurmur3_128(ExecutionPlan plan) {

    for (int i = plan.iterations; i > 0; i--) {
        plan.murmur3.putString(plan.password, Charset.defaultCharset());
    }

    plan.murmur3.hash();
}

Здесь полеiterations будет заполнено соответствующими значениями из аннотации@Param от JMH, когда оно будет передано методу тестирования. Аннотированный метод@Setup вызывается перед каждым вызовом теста и создает новыйHasher, обеспечивающий изоляцию.

Когда выполнение будет завершено, мы получим результат, аналогичный приведенному ниже:

# Run complete. Total time: 00:06:47

Benchmark                   (iterations)   Mode  Cnt      Score      Error  Units
BenchMark.benchMurmur3_128           100  thrpt   20  92463.622 ± 1672.227  ops/s
BenchMark.benchMurmur3_128           200  thrpt   20  39737.532 ± 5294.200  ops/s
BenchMark.benchMurmur3_128           300  thrpt   20  30381.144 ±  614.500  ops/s
BenchMark.benchMurmur3_128           500  thrpt   20  18315.211 ±  222.534  ops/s
BenchMark.benchMurmur3_128          1000  thrpt   20   8960.008 ±  658.524  ops/s

5. Заключение

В этом руководстве основное внимание уделяется средствам тестирования микро-тестов Java и демонстрируется их.

Как всегда, примеры кода можно найтиon GitHub.