Микробенчмаркинг с 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.