Как написать правильный микро-тест в Java?
Как вы пишете (и запускаете) правильный микро-тест в Java?
Я ищу некоторые примеры кода и комментарии, иллюстрирующие различные вещи, чтобы думать.
Пример: должен ли эталон измерять время/итерацию или итерации/время и почему?
Связанный: приемлем ли сравнительный анализ секундомера?
Ответы
Ответ 1
Советы по написанию микро-тестов от создателей 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
уменьшить накладные расходы GC, установите Xmx
(достаточно большой) равным Xms
и используйте UseEpsilonGC
если он доступен.
Правило 8: используйте библиотеку для своего теста, поскольку она, вероятно, более эффективна и уже отлажена для этой единственной цели. Такие как JMH, Caliper или Bill and Paul Превосходные тесты UCSD для Java.
Ответ 2
Я знаю, что этот вопрос помечен как ответивший, но я хотел бы упомянуть две библиотеки, которые помогают нам писать микро-тесты
Штангенциркуль от Google
Учебники по началу работы
- http://codingjunkie.net/micro-benchmarking-with-caliper/
- http://vertexlabs.co.uk/blog/caliper
JMH из OpenJDK
Учебники по началу работы
- Как избежать подводных камней в JVM
- http://nitschinger.at/Using-JMH-for-Java-Microbenchmarking
- http://java-performance.info/jmh/
Ответ 3
Важные вещи для тестов Java:
- Сначала прогрейте JIT, запустив код несколько раз, прежде чем синхронизировать его
- Убедитесь, что вы используете его достаточно долго, чтобы иметь возможность измерять результаты в секундах или (лучше) десятках секунд
- Хотя вы не можете вызывать
System.gc()
между итерациями, было бы неплохо запускать его между тестами, так что каждый тест, мы надеемся, получит "чистое" пространство памяти для работы. (Да, gc()
- скорее подсказка, чем гарантия, но вполне вероятно, что она действительно будет собирать мусор в моем опыте.) - Мне нравится отображать итерации и время, а также счет времени/итерации, который можно масштабировать так, чтобы "лучший" алгоритм получил оценку 1,0, а другие оценивали относительно. Это означает, что вы можете запускать все алгоритмы в течение длительного времени, варьируя как количество итераций, так и время, но при этом получая сопоставимые результаты.
Я только в процессе ведения блога о разработке платформы для тестирования в .NET. У меня есть несколько предыдущих постов, которые могут дать вам некоторые идеи - конечно, не все будет уместно, но некоторые из них могут быть.
Ответ 4
jmh является недавним дополнением к OpenJDK и написано некоторыми инженерами по производительности из Oracle. Конечно, стоит посмотреть.
JMH - это Java-система для построения, запуска и анализа нано/микро/макро тестов, написанных на Java и других языках, предназначенных для JVM.
Очень интересные фрагменты информации похоронены в примерах тестовых комментариев.
Смотрите также:
Ответ 5
Должен ли эталон измерять время/итерацию или итерации/время и почему?
Это зависит от того, что вы пытаетесь проверить.
Если вас интересует задержка, используйте время/итерацию, а если вас интересует пропускная способность, используйте итерации/время.
Ответ 6
Если вы пытаетесь сравнить два алгоритма, сделайте по крайней мере два теста для каждого, чередуя порядок. то есть:
for(i=1..n)
alg1();
for(i=1..n)
alg2();
for(i=1..n)
alg2();
for(i=1..n)
alg1();
Я обнаружил некоторые заметные различия (иногда 5-10%) во время выполнения одного и того же алгоритма на разных проходах.
Кроме того, убедитесь, что n очень большое, чтобы время выполнения каждого цикла составляло как минимум 10 секунд или около того. Чем больше итераций, тем значительнее показатели времени тестирования и тем надежнее данные.
Ответ 7
Убедитесь, что вы каким-то образом используете результаты, которые вычисляются в контрольном коде. В противном случае ваш код можно будет оптимизировать.
Ответ 8
Есть много возможных ошибок для написания микро-тестов в Java.
Во-первых: вам нужно рассчитать всевозможные события, которые занимают время более или менее случайным образом: сбор мусора, эффекты кеширования (ОС для файлов и процессора для памяти), IO и т.д.
Второе: вы не можете доверять точности измеренных времен для очень коротких интервалов.
В-третьих: JVM оптимизирует ваш код во время выполнения. Таким образом, разные прогоны в одном JVM-экземпляре будут быстрее и быстрее.
Мои рекомендации. Сделайте контрольный тест за несколько секунд, что более надежно, чем время выполнения за миллисекунды. Разогрейте JVM (это означает, что хотя бы один раз тестируйте бенчмарк без измерения, JVM может запускать оптимизацию). И запустите свой тест несколько раз (может быть, 5 раз) и возьмите медианную ценность. Запуск каждого микро-теста в новом JVM-экземпляре (вызов для каждого теста новой Java), в противном случае эффекты оптимизации JVM могут повлиять на последующие тесты. Не выполняйте действия, которые не выполняются в фазе прогрева (поскольку это может вызвать загрузку классов и перекомпиляцию).
Ответ 9
Следует также отметить, что также может быть важно проанализировать результаты микро-теста при сравнении различных реализаций. Поэтому следует сделать <значимый тест .
Это связано с тем, что реализация A
может быть быстрее во время большинства этапов теста, чем реализация B
. Но A
может также иметь более высокий разброс, поэтому измеренное преимущество производительности A
не будет иметь никакого значения по сравнению с B
.
Таким образом, также важно правильно записать и запустить микро-тест, а также правильно проанализировать его.
Ответ 10
http://opt.sourceforge.net/ Java Micro Benchmark - контроль задач, необходимых для определения сравнительных характеристик производительности компьютерной системы на разных платформах. Может использоваться для управления решениями по оптимизации и для сравнения различных реализаций Java.
Ответ 11
Чтобы добавить к другому отличному совету, я также помню следующее:
Для некоторых процессоров (например, 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");