Тестирование производительности с использованием jmh при весенней загрузке

оптимизация производительности

последовательность

Я давно работаю в CRUD и вдруг однажды мне небезразличен мой проект.qpsготовые к использованиюjmeterПротестировали самый посещаемый интерфейс и обнаружили, что только плохой17r/sСлева и справа... смотри, миллионы в сетиqps, мне стыдно.

Итак, мы готовы выполнить оптимизацию производительности, но нам нужно выполнить тестирование производительности перед оптимизацией, чтобы узнать, каков эффект оптимизации.Например, мой первый шаг — использоватьredisПосле добавления кеша результат теста показал, что он на самом деле медленнее, чем до того, как его не добавили??? Так что тест производительности - очень важная часть, иjmhЭто очень удобный инструмент для тестирования производительности.

Готов к работе

Подготовительная работа очень проста, введениеjmhизmavenПросто пакет.

        <dependency>
            <groupId>org.openjdk.jmh</groupId>
            <artifactId>jmh-core</artifactId>
            <version>1.22</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.openjdk.jmh</groupId>
            <artifactId>jmh-generator-annprocess</artifactId>
            <version>1.22</version>
            <scope>provided</scope>
        </dependency>

первый пример

package jmh;

import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;

/**
 * Benchmark
 *
 * @author wangpenglei
 * @since 2019/11/27 13:54
 */
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@State(Scope.Benchmark)
public class Benchmark {

    public static void main(String[] args) throws Exception {
        // 使用一个单独进程执行测试,执行5遍warmup,然后执行5遍测试
        Options opt = new OptionsBuilder().include(Benchmark.class.getSimpleName()).forks(1).warmupIterations(5)
                .measurementIterations(5).build();
        new Runner(opt).run();
    }

    @Setup
    public void init() {
   
    }

    @TearDown
    public void down() {

    }

    @org.openjdk.jmh.annotations.Benchmark
    public void test() {

    }

}

@BenchmarkMode

Это предназначено для определения тестового режима.В Интернете есть много конкретного контента.То, что я использую здесь,Рассчитать среднее время работы

@OutputTimeUnit(TimeUnit.MILLISECONDS)

Эта аннотация является единицей конечного результата вывода. Поскольку тест представляет собой интерфейс, я использую миллисекунды. Если это локальный тестredisИли нативный метод, который можно заменить меньшим блоком.

@State(Scope.Benchmark)

Эта аннотация определяет доступную область для данного экземпляра класса, потому чтоspringвнутреннийbeanПо умолчанию используется синглтон, поэтому я использую здесьВсе потоки, выполняющие один и тот же тест, будут совместно использовать этот экземпляр. Может использоваться для тестирования многопоточной производительности объектов состояния (или просто отметить эталон для этого диапазона).

@Setup @TearDown @Benchmark

Очень простая аннотация, обычные тесты имеютИнициализировать перед тестированием*Очистка ресурсов после тестирования**Методы испытаний*.

Как использовать с пружиной

потому что нам нужноspringсреду, чтобы протестироватьbean, поэтому вам нужно вручную создать его в методе инициализации.Я проверил информацию и не нашел лучшего метода, поэтому сначала создам его вручную.

    private ConfigurableApplicationContext context;
    private AppGoodsController controller;

    @Setup
    public void init() {
        // 这里的WebApplication.class是项目里的spring boot启动类
        context = SpringApplication.run(WebApplication.class);
        // 获取需要测试的bean
        this.controller = context.getBean(AppGoodsController.class);
    }

    @TearDown
    public void down() {
        context.close();
    }

начать тестирование

Начать после написания тестового методаmainМетод протестирован, и теперь будут выдаваться какие-то странные ошибки, но мне все равно, если это не повлияет на результаты.После завершения операции результаты будут выведены, а эффект от оптимизации можно будет сравнить на этом время.

Result "jmh.Benchmark.testGetGoodsList":
  65.969 ±(99.9%) 10.683 ms/op [Average]
  (min, avg, max) = (63.087, 65.969, 69.996), stdev = 2.774
  CI (99.9%): [55.286, 76.652] (assumes normal distribution)


# Run complete. Total time: 00:02:48

REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on
why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial
experiments, perform baseline and negative tests that provide experimental control, make sure
the benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts.
Do not assume the numbers tell you what you want them to tell.

Benchmark                   Mode  Cnt   Score    Error  Units
Benchmark.testGetGoodsList  avgt    5  65.969 ± 10.683  ms/op

Process finished with exit code 0

Отрицательная оптимизация в начале

В начале статьи я привел пример негативной оптимизации, я использовалredisПосле добавления кеша он реально медленнее чем прямой запрос к БД!На самом деле причина очень проста.Проверил на локальном компе и подключилredisТем не менее, она развернута на сервере.Таким образом, сетевая задержка в и из общедоступной сети уже очень большая.Однако база данных также проходит через общедоступную сеть, и это не будет сравниваться с общедоступной сетью.redisБыстро — это правильно. Последняя причина — обнаружить развертываниеredisпропускная способность сервера составляет всего1mэто100kb/s, его легко заполнить. Окончательная оптимизацияredisДобавьте кеш и используйте подключение к интрасетиredis.

Результаты оптимизации

Скорость до оптимизации:

Result "jmh.Benchmark.testGetGoodsList":
  102.419 ±(99.9%) 153.083 ms/op [Average]
  (min, avg, max) = (65.047, 102.419, 162.409), stdev = 39.755
  CI (99.9%): [≈ 0, 255.502] (assumes normal distribution)


# Run complete. Total time: 00:03:03

REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on
why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial
experiments, perform baseline and negative tests that provide experimental control, make sure
the benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts.
Do not assume the numbers tell you what you want them to tell.

Benchmark                   Mode  Cnt    Score     Error  Units
Benchmark.testGetGoodsList  avgt    5  102.419 ± 153.083  ms/op

Process finished with exit code 0

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

Result "jmh.Benchmark.testGetGoodsList":
  29.210 ±(99.9%) 2.947 ms/op [Average]
  (min, avg, max) = (28.479, 29.210, 30.380), stdev = 0.765
  CI (99.9%): [26.263, 32.157] (assumes normal distribution)


# Run complete. Total time: 00:02:49

REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on
why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial
experiments, perform baseline and negative tests that provide experimental control, make sure
the benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts.
Do not assume the numbers tell you what you want them to tell.

Benchmark                   Mode  Cnt   Score   Error  Units
Benchmark.testGetGoodsList  avgt    5  29.210 ± 2.947  ms/op

Process finished with exit code 0

Вы можете видеть, что это почти3.5На самом деле, пространство для оптимизации еще есть, и все операции с БД проходят черезredisКэш бы наверное1msЭто сделано.