Статьи

Java Micro-Benchmarking: Как правильно писать тесты

Несколько месяцев назад я написал статью для сравнения характеристик коротких индексов для циклов . Я спросил себя о представлениях, использующих шорты в качестве индексов цикла для цикла с несколькими итерациями.

В языке Java все операции над целыми числами производятся в int. Таким образом, если мы будем использовать индекс short as loop, на каждой итерации будет производиться типизация, что на самом деле тяжелее простого воздействия на int.

Я написал этот код для достижения моей цели:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.wicht.old;
  
public class TestShortInt {
    public static void main(String[] args){
        long startTime = System.nanoTime();
  
        int resultInt = 0;
  
        for (int i = 0; i < 100000; i++){
            for (int j = 0; j < 32760; j++){
                resultInt += i * j;
            }
        }
  
        System.out.println("Temp pour int : " + (System.nanoTime() - startTime) / 1000000 + " ms");
  
        startTime = System.nanoTime();
  
        int resultShort = 0;
  
        for (int k = 0; k < 100000; k++){
            for (short f = 0; f < 32760; f++){
                resultShort += k * f;
            }
        }
  
        System.out.println("Temp pour short : " + (System.nanoTime() - startTime) / 1000000 + " ms");
  
        System.out.println(resultInt);
        System.out.println(resultShort);
    }
}

И в результате я обнаружил, что short был в два раза медленнее, чем int, и я был убежден в этих результатах до недели назад.

В это время читатель (Жан) раскритиковал результаты моих тестов и дал мне ссылки на несколько статей о микро-бенчмаркинге . Я прочитал эти статьи и понимаю, почему мои результаты были неверными.

Фактически, мой тест не обращает внимания на несколько вещей, которые могут изменить результаты тестов:

  • Разминка JVM : из-за нескольких параметров код сначала часто медленен и становится все быстрее и быстрее, когда время выполнения увеличивается до тех пор, пока оно не перейдет в устойчивое состояние.
  • Загрузка классов : при первом запуске бенчмарка все используемые классы должны быть загружены, что увеличивает время выполнения.
  • Компилятор Just In Time : когда JVM идентифицирует горячую часть кода
  • Сборщик мусора : Сборка мусора может происходить во время теста, и с этим время может значительно увеличиться.

Из-за всех этих факторов первые прогоны (возможно, 10 секунд пробега) медленнее других и могут сделать ваши тесты полностью ложными.

Итак, как мы можем добиться хороших результатов тестов?

Это действительно сложно, но мы можем помочь в использовании эталонной среды, представленной Брентом Бойером, разработчиком программного обеспечения из Elliptic Group. Эта структура учитывает все ранее введенные факторы и делает хорошие контрольные показатели.

Использовать этот фреймворк очень просто: вам просто нужно создать новый экземпляр класса Benchmark, передав ему Callable или Runnable, и тест будет запущен напрямую. Вот пример с тестом short и int в индексах цикла:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class ShortIndexesLoop {
    public static void main(String[] args) {
        Callable callableInt = new Callable(){
            public Long call() throws Exception {
                long result = 0;
  
                for (int f = 0; f < 32760; f++){
                      result += 444;
                  }
  
                return result;
            }
        };
  
        Callable callableShort = new Callable(){
            public Long call() throws Exception {
                long result = 0;
  
                for (short f = 0; f < 32760; f++){
                      result += 444;
                  }
  
                return result;
            }
        };
  
        try {
            Benchmark intBenchmark = new Benchmark(callableInt);
  
            System.out.println("Result with int ");
            System.out.println(intBenchmark.toString());
  
            Benchmark shortBenchmark = new Benchmark(callableShort);
  
            System.out.println("Result short ");
            System.out.println(shortBenchmark.toString());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Чтобы получить результаты, вы можете использовать Benchmark.toString () или Benchmark.toStringFull () для получения дополнительной статистики. Вы также можете напрямую получить доступ к некоторым статистическим данным, таким как стандартное отклонение, используя Benchmark.getSd () или напрямую с помощью Benchmark.getStats (), чтобы получить всю статистику.

Вот результат с предыдущим кодом:

Результат int first = 807.056 us, среднее = 46.032 us (дельты CI: -261.393 нс, +408.932 нс), sd = 230.929 us (дельты CI: -68.201 us, +105.262 us)
Результат короткий первый = 721,912 долл. США, среднее значение = 48,234 долл. США (дельта CI: -198,625 нс, +254,774 нс), сд = 160,196 долл. США (дельта CI: -32,764 долл. США, +37,882 долл. США)

Как видите, короткая версия всего на 104.78% медленнее, чем int. Это показывает, что первые результаты были полностью ложными.

Вот полные результаты версии int:

статистика действий: первая = 807,056 мкс, средняя = 46,032 мкс (дельта CI: -261,393 нс, +408,932 нс), sd = 230,929 мкс (дельта CI: -68,21 мкс, +105,262 мкс) ЗНАЧЕНИЯ МОГУТ БЫТЬ НЕ ТОЧНЫМИ ———- — статистика действий была рассчитана из статистики блоков — каждый блок измерял 32768 выполнений задач — пользователь говорит, что задача внутренне выполняет m = 1 действий — тогда число действий на измерение блока составляет = 32768 — статистика блока: среднее значение = 1,508 с (дельта CI: -8,565 мс, +13,400 мс), SD = 41,803 мс (дельта CI: -12,346 мс, +19,054 мс) — форум, используемый для преобразования статистики блока в статистику действий (средние масштабы) как 1 / a, sd масштабируется как 1 / sqrt (a)) предполагает, что времена выполнения действия iid ———– — каждый доверительный интервал (CI) сообщается либо как + — дельты от оценки точки, либо как закрытый интервал ([x, y]) — каждый доверительный интервал имеет доверительный уровень = 0,95 это было определено с использованием алгоритма коробчатого графика с медианой = 1.498 с, interquantileRange = 34.127 мс –3 являются ОЧЕНЬ экстремальными (с высокой стороны): # 57 = 1.621 с, # 58 = 1.647 с, # 59 = 1.688 с –2 мягкие ( на высокой стороне): # 55 = 1,570 с, # 56 = 1,582 с —блок-значения sd блока МОГУТ НЕ ОТРАЖАТЬ ВНУТРЕННЕЕ ИЗМЕНЕНИЕ ЗАДАЧИ — оценка: шум окружающей среды объясняет не менее 55,8941862187682222% от измеренного sd ———- — Значения sd действия ПОЧТИ МОЖНО БЕЗУМНО НАДВИЖЕНЫ выбросами — они вызывают не менее 98,95646276911543% от измеренного ВАРИАНТА согласно эквивалентной модели выбросов — количества моделей: a = 32768.0, muB = 1.5083895562166663, sigmaB = 0.041802646686166164676776771482674674164163483,1683,1683,1683,1683,1683 по номеру устройства: , sigmaA = 2.3092919283255957E-4, Tmin = 0,0, muGMin = 2.3016198062388096E-5, sigmaG = 5.754049515597024E-6, cMax1 = 1252, cMax2 = 322, Cmax = 322, cOutMin = 322, varOutMin = +0,0017292260645147487, MUG (cOutMin) = 2,3034259031465023E-5, U (cOutMin) = 0,002363416110812895

Как вы, возможно, можете видеть, когда используете эту платформу, она дает вам некоторые предупреждения, когда, например, у вас есть экстремальные выбросы, которые могут сделать стандартное отклонение полностью ложным.

Вы можете скачать эту структуру на веб-странице Elliptic Group . Я нашел его действительно мощным и простым в использовании, и буду использовать его каждый раз, когда мне нужно будет сделать тест.

В заключение я также должен сказать, что даже если вы используете такую ​​среду, вы можете делать очень плохие тесты, если не тестируете правильную часть кода. Вот две действительно интересные статьи от Брента Бойера:

Ссылка: Как написать правильные тесты от нашего партнера JCG Баптиста Вихта в

Статьи по Теме :