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

728

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

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

Пример. Если контрольная точка измеряет время/итерацию или итерации/время и почему?

Связано: Допустимо ли использование бенчмаркинга секундомера?

Показать ещё 5 комментариев
Теги:
jvm
benchmarking
microbenchmark
jvm-hotspot

11 ответов

710
Лучший ответ

Советы по написанию микро-тестов от создателей 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 для предотвращения параллельной работы компилятора с самим собой. Постарайтесь, чтобы уменьшить накладные расходы GC, установить Xmx (достаточно большой) равным Xms и использовать UseEpsilonGC если он доступен.

Правило 8: Используйте библиотеку для своего теста, поскольку она, вероятно, более эффективна и уже была отлажена для этой единственной цели. Такие, как JMH, Caliper или Bill and Paul Excellent UCSD Benchmarks для Java.

  • 5
    Это была также интересная статья: ibm.com/developerworks/java/library/j-jtp12214
  • 126
    Кроме того, никогда не используйте System.currentTimeMillis (), если у вас нет проблем с точностью + или - 15 мс, что типично для большинства комбинаций OS + JVM. Вместо этого используйте System.nanoTime ().
Показать ещё 4 комментария
226

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

Суппорт с 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/
  • 34
    +1 это можно было бы добавить как правило 8 принятого ответа: правило 8: поскольку многие вещи могут пойти не так, вам, вероятно, следует использовать существующую библиотеку, а не пытаться делать это самостоятельно!
  • 8
    @Pangea jmh, вероятно, в настоящее время превосходит Caliper, см. Также: groups.google.com/forum/#!msg/mechanical-sympathy/m4opvy4xq3U/…
77

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

  • Сначала разогрейте JIT, запустив код несколько раз, прежде чем синхронизировать его.
  • Убедитесь, что вы запустили его достаточно долго, чтобы иметь возможность измерять результаты в секундах или (лучше) десятки секунд.
  • Пока вы не можете вызвать System.gc() между итерациями, неплохо запустить его между тестами, чтобы каждый тест, надеюсь, получил "чистую" память для работы. (Да, gc() - скорее намек, чем гарантия, но очень вероятно, что он действительно будет собирать мусор в моем опыте.)
  • Мне нравится показывать итерации и время, а также счет времени/итерации, который можно масштабировать таким образом, чтобы "лучший" алгоритм получал оценку 1.0, а другие оценивались относительным образом. Это означает, что вы можете запускать все алгоритмы в течение длительного времени, изменяя как количество итераций, так и время, но все же получая сопоставимые результаты.

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

  • 3
    Незначительная мелочь: IMO «чтобы каждый тест получал» должен быть «так, чтобы каждый тест мог получить», так как первый создает впечатление, что вызов gc всегда освобождает неиспользуемую память.
  • 0
    @ SanjayT.Sharma: Ну, намерение состоит в том, что это действительно так. Хотя это не строго гарантировано, на самом деле это довольно сильный намек. Будет редактировать, чтобы быть более понятным.
Показать ещё 7 комментариев
39

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

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

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

См. также:

17

Если контрольная точка измеряет время/итерацию или итерации/время и почему?

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

14

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

12

Есть много возможных ошибок для написания микро-тестов в Java.

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

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

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

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

12

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

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

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

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

  • 5
    Естественно, изменение порядка влияет на время выполнения. JVM-оптимизация и кеширование будут работать здесь. Лучше «прогреть» JVM-оптимизацию, сделать несколько прогонов и сравнить каждый тест в другой JVM.
7

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

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

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

6

http://opt.sourceforge.net/ Java Micro Benchmark - контроль задач, необходимых для определения сравнительных характеристик производительности компьютерной системы на разных платформах. Может использоваться для управления решениями по оптимизации и для сравнения различных реализаций Java.

  • 1
    Кажется, просто для сравнения оборудования JVM +, а не произвольный кусок кода Java.
5

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

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

Возможно, более принципиально важный аспект, который у вас есть прямой контроль: убедитесь, что вы правильно оцениваете! Например, если вы используете 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");
  • 0
    Да, важно не выполнять несвязанную работу внутри временной области, но ваш первый пример все еще в порядке. Существует только один вызов println , а не отдельная строка заголовка или что-то еще, и System.nanoTime() должен быть оценен как первый шаг в построении строкового аргумента для этого вызова. С первым компилятор не может ничего поделать со вторым, и никто даже не побуждает их делать дополнительную работу перед записью времени остановки.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню