Как мне написать правильный микро-тест в Java? - PullRequest
804 голосов
/ 02 февраля 2009

Как вы пишете (и запускаете) правильный микро-тест на Java?

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

Пример: должен ли эталон измерять время / итерацию или итерации / время и почему?

Похожие: Допустим ли сравнительный анализ секундомера?

Ответы [ 11 ]

731 голосов
/ 04 февраля 2009

Советы по написанию микро-тестов от создателей Java HotSpot :

Правило 0: Прочтите авторитетную статью о JVM и микробенчмаркингах Хороший - Брайан Гетц, 2005 . Не ожидайте слишком многого от микро-тестов; они измеряют только ограниченный диапазон рабочих характеристик JVM.

Правило 1: Всегда включайте фазу прогрева, которая запускает ваше тестовое ядро ​​на всем протяжении, достаточное для запуска всех инициализаций и компиляций до фазы (фаз) синхронизации. (Меньше итераций в порядке на этапе разогрева. Основное правило - несколько десятков тысяч итераций внутреннего цикла.)

Правило 2: Всегда запускайте с -XX:+PrintCompilation, -verbose:gc и т. Д., Чтобы вы могли убедиться, что компилятор и другие части JVM не выполняют неожиданную работу во время фазы синхронизации.

Правило 2.1: Печатайте сообщения в начале и в конце фаз хронирования и прогрева, чтобы можно было убедиться, что в фазе тактирования нет выходных данных из правила 2.

Правило 3: Имейте в виду разницу между -client и -server и OSR и регулярными компиляциями. Флаг -XX:+PrintCompilation сообщает о компиляции OSR со знаком at, обозначающим не начальную точку входа, например: Trouble$1::run @ 2 (41 bytes). Предпочитайте сервер клиенту и обычное OSR, если вам нужна лучшая производительность.

Правило 4: Помните об эффектах инициализации. Не печатайте в первый раз во время фазы синхронизации, так как печать загружает и инициализирует классы. Не загружайте новые классы вне фазы прогрева (или финальной фазы отчетности), если только вы не тестируете загрузку классов специально (а в этом случае загружаете только тестовые классы). Правило 2 - ваша первая линия защиты от таких эффектов.

Правило 5: Имейте в виду эффекты деоптимизации и перекомпиляции. Не используйте какой-либо путь к коду в первый раз на этапе синхронизации, потому что компилятор может создать нежелательную и перекомпилировать код, основываясь на более раннем оптимистическом предположении, что этот путь вообще не будет использоваться. Правило 2 - ваша первая линия защиты от таких эффектов.

Правило 6: Используйте соответствующие инструменты, чтобы прочитать мысли компилятора и ожидать, что вы будете удивлены кодом, который он генерирует. Проверьте код самостоятельно, прежде чем создавать теории о том, что делает что-то быстрее или медленнее.

Правило 7: Уменьшите шум в ваших измерениях. Запустите свой тест на тихой машине и запустите его несколько раз, отбрасывая выбросы. Используйте -Xbatch для сериализации компилятора с приложением и рассмотрите установку -XX:CICompilerCount=1 для предотвращения параллельной работы компилятора. Старайтесь изо всех сил уменьшить накладные расходы ГХ, установите Xmx (достаточно большой) равным Xms и используйте UseEpsilonGC, если он доступен.

Правило 8: Используйте библиотеку для своего эталонного теста, поскольку она, вероятно, более эффективна и уже отлажена для этой единственной цели. Например, JMH , Caliper или Отличный тест UCSD Билла и Пола для Java .

231 голосов
/ 19 декабря 2010

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

Штангенциркуль от Google

Учебники по началу работы

  1. http://codingjunkie.net/micro-benchmarking-with-caliper/
  2. http://vertexlabs.co.uk/blog/caliper

JMH от OpenJDK

Учебники по началу работы

  1. Избегание ловушек на JVM
  2. http://nitschinger.at/Using-JMH-for-Java-Microbenchmarking
  3. http://java -performance.info / JMH /
82 голосов
/ 02 февраля 2009

Важными моментами для тестов Java являются:

  • Сначала прогрейте JIT, запустив код несколько раз до времени it
  • Убедитесь, что вы используете его достаточно долго, чтобы иметь возможность измерять результаты в секундах или (лучше) десятках секунд
  • Несмотря на то, что вы не можете вызывать System.gc() между итерациями, рекомендуется запускать его между тестами, чтобы каждый тест получал «чистую» область памяти для работы. (Да, gc() является скорее подсказкой, чем гарантией, но весьма вероятно, , что она действительно будет собирать мусор в моем опыте.)
  • Мне нравится отображать итерации и время, а также счет времени / итерации, который можно масштабировать так, чтобы «лучший» алгоритм получил оценку 1,0, а другие оценивали относительно. Это означает, что вы можете запускать все алгоритмы в течение длительного времени, варьируя как количество итераций, так и время, но при этом получая сопоставимые результаты.

Я только что веду блог о разработке инфраструктуры для тестирования в .NET. У меня есть пара из предыдущих сообщений , которые могут дать вам некоторые идеи - конечно, не все будет уместно, но некоторые из них могут быть.

47 голосов
/ 03 апреля 2013

jmh - недавнее дополнение к OpenJDK, написанное некоторыми инженерами по производительности из Oracle. Конечно, стоит посмотреть.

jmh - это инструмент Java для сборки, запуска и анализа нано / микро / макро тестов, написанных на Java и других языках, предназначенных для JVM.

Очень интересные фрагменты информации, спрятанные в в комментариях к тестам .

Смотри также:

20 голосов
/ 02 февраля 2009

Должен ли эталон измерять время / итерацию или итерации / время и почему?

Это зависит от что вы пытаетесь проверить.

Если вас интересует задержка , используйте время / итерацию, а если вас интересует пропускная способность , используйте итерации / время.

15 голосов
/ 02 февраля 2009

Убедитесь, что вы каким-то образом используете результаты, которые вычисляются в тестируемом коде. В противном случае ваш код может быть оптимизирован.

14 голосов
/ 02 февраля 2009

Если вы пытаетесь сравнить два алгоритма, сделайте как минимум два теста для каждого, чередуя порядок. i.e.:

for(i=1..n)
  alg1();
for(i=1..n)
  alg2();
for(i=1..n)
  alg2();
for(i=1..n)
  alg1();

Я обнаружил некоторые заметные различия (иногда 5-10%) во время выполнения одного и того же алгоритма на разных проходах.

Кроме того, убедитесь, что n очень большой, чтобы время выполнения каждого цикла составляло как минимум 10 секунд или около того. Чем больше итераций, тем значительнее показатели времени тестирования и тем надежнее данные.

13 голосов
/ 02 февраля 2009

Существует множество возможных ловушек для написания микро-тестов в Java.

Во-первых: вы должны рассчитывать со всеми видами событий, которые занимают более или менее случайное время: сборка мусора, эффекты кэширования (ОС для файлов и ЦП для памяти), IO и т. Д.

Второе: вы не можете доверять точности измеренного времени для очень коротких интервалов.

В-третьих: JVM оптимизирует ваш код во время выполнения. Так что разные прогоны в одном и том же экземпляре JVM будут становиться все быстрее и быстрее.

Мои рекомендации: сделайте тест производительности на несколько секунд, что более надежно, чем время выполнения в течение миллисекунд. Прогрейте JVM (это означает, что хотя бы один раз тест будет выполнен без измерения, чтобы JVM могла выполнять оптимизацию). И проведите свой тест несколько раз (возможно, 5 раз) и возьмите среднее значение. Запустите каждый микропроцессор в новом экземпляре JVM (вызовите каждый тест нового Java), иначе эффекты оптимизации JVM могут повлиять на последующие выполняемые тесты. Не выполняйте вещи, которые не выполняются в фазе разогрева (поскольку это может вызвать загрузку классов и перекомпиляцию).

8 голосов
/ 21 января 2013

Следует также отметить, что также может быть важно проанализировать результаты микропроцессора при сравнении различных реализаций. Поэтому следует провести значительный тест .

Это связано с тем, что реализация A может выполняться быстрее в большинстве прогонов теста, чем реализация B. Но A может также иметь более высокий спред, поэтому измеренное преимущество в производительности A не будет иметь никакого значения по сравнению с B.

Поэтому важно также правильно написать и запустить микро-тест, а также правильно его проанализировать.

7 голосов
/ 19 марта 2017

Чтобы добавить к другому отличному совету, я бы также помнил следующее:

Для некоторых процессоров (например, Intel Core i5 с TurboBoost) температура (и количество используемых в настоящее время ядер, а также процент их использования) влияет на тактовую частоту. Поскольку процессоры синхронизируются динамически, это может повлиять на ваши результаты. Например, если у вас однопоточное приложение, максимальная тактовая частота (с TurboBoost) выше, чем для приложения, использующего все ядра. Поэтому это может помешать сравнениям однопоточной и многопоточной производительности в некоторых системах. Имейте в виду, что температура и напряжение также влияют на то, как долго поддерживается турбо частота.

Возможно, это более важный аспект, который вы непосредственно контролируете: убедитесь, что вы измеряете правильную вещь! Например, если вы используете System.nanoTime() для сравнения определенного фрагмента кода, размещайте вызовы для назначения в местах, которые имеют смысл, чтобы избежать измерения вещей, которые вас не интересуют. Например, не делайте

long startTime = System.nanoTime();
//code here...
System.out.println("Code took "+(System.nanoTime()-startTime)+"nano seconds");

Проблема в том, что вы не сразу получаете время окончания, когда код закончен. Вместо этого попробуйте следующее:

final long endTime, startTime = System.nanoTime();
//code here...
endTime = System.nanoTime();
System.out.println("Code took "+(endTime-startTime)+"nano seconds");
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...