Как вы пишете (и запускаете) правильный микро-тест в Java?
Я ищу некоторые примеры кода и комментарии, иллюстрирующие различные вещи, чтобы думать.
Пример: должен ли эталон измерять время / итерацию или итерации / время и почему?
Как вы пишете (и запускаете) правильный микро-тест в Java?
Я ищу некоторые примеры кода и комментарии, иллюстрирующие различные вещи, чтобы думать.
Пример: должен ли эталон измерять время / итерацию или итерации / время и почему?
Ответы:
Советы по написанию микро-тестов от создателей Java HotSpot :
Правило 0: Прочтите авторитетную статью о JVM и микробенчмаркинге. Хорошим является Брайан Гетц, 2005 . Не ожидайте слишком многого от микро-тестов; они измеряют только ограниченный диапазон рабочих характеристик JVM.
Правило 1: всегда включайте фазу разминки, которая запускает ваше тестовое ядро на всем протяжении, достаточное для запуска всех инициализаций и компиляций до фазы (фаз) синхронизации. (Меньше итераций в порядке на этапе разогрева. Основное правило - несколько десятков тысяч итераций внутреннего цикла.)
Правило 2: всегда выполняйте с -XX:+PrintCompilation
, -verbose:gc
и т. Д., Чтобы вы могли убедиться, что компилятор и другие части JVM не выполняют неожиданную работу во время фазы синхронизации.
Правило 2.1. Печатайте сообщения в начале и в конце фаз синхронизации и прогрева, чтобы можно было убедиться, что в фазе синхронизации нет выходных данных из правила 2.
Правило 3: помните о разнице между -client
и -server
, и OSR, и регулярными компиляциями. -XX:+PrintCompilation
Флаг сообщает ЛРН компиляций с при-знаком для обозначения без начальной точки входа, например: Trouble$1::run @ 2 (41 bytes)
. Предпочитайте сервер клиенту, а обычное - OSR, если вы стремитесь к лучшей производительности.
Правило 4: знать об эффектах инициализации. Не печатайте в первый раз во время фазы синхронизации, так как печать загружает и инициализирует классы. Не загружайте новые классы за пределами фазы прогрева (или финальной фазы отчетности), если только вы не тестируете загрузку классов специально (а в этом случае загружаете только тестовые классы). Правило 2 - ваша первая линия защиты от таких эффектов.
Правило 5: знать о последствиях деоптимизации и перекомпиляции. Не используйте какой-либо путь к коду в первый раз на этапе синхронизации, потому что компилятор может создать нежелательную и перекомпилировать код, основываясь на более раннем оптимистическом предположении, что путь вообще не будет использоваться. Правило 2 - ваша первая линия защиты от таких эффектов.
Правило 6: Используйте соответствующие инструменты, чтобы прочитать мысли компилятора и ожидать, что вы будете удивлены кодом, который он создает. Проверьте код самостоятельно, прежде чем создавать теории о том, что делает что-то быстрее или медленнее.
Правило 7: уменьшите шум в ваших измерениях. Запустите тест на тихой машине и запустите его несколько раз, отбрасывая выбросы. Используйте -Xbatch
для сериализации компилятора с приложением и рассмотрите возможность настройки, -XX:CICompilerCount=1
чтобы компилятор не работал параллельно с самим собой. Старайтесь изо всех сил, чтобы уменьшить накладные расходы GC, установить Xmx
(достаточно большой) равных Xms
и использовать, UseEpsilonGC
если он доступен.
Правило 8: используйте библиотеку для своего теста, поскольку она, вероятно, более эффективна и уже отлажена для этой единственной цели. Такие как JMH , Caliper или Билл и превосходные тесты UCSD Пола для Java .
System.nanoTime()
не гарантируется, что будет более точным, чем System.currentTimeMillis()
. Это гарантированно будет, по крайней мере, так же точно. Однако обычно это значительно точнее.
System.nanoTime()
вместо того System.currentTimeMillis()
, чтобы быть, является то, что первое гарантированно будет монотонно увеличиваться. Вычитание значений, возвращаемых двумя currentTimeMillis
вызовами, может фактически дать отрицательные результаты, возможно, потому что системное время было настроено некоторым демоном NTP.
Я знаю, что этот вопрос был помечен как ответивший, но я хотел бы упомянуть две библиотеки, которые помогают нам писать микро тесты
Начало обучения
Начало обучения
Важные вещи для тестов Java:
System.gc()
между итерациями, рекомендуется запускать его между тестами, чтобы каждый тест получал «чистое» пространство памяти для работы. (Да, gc()
это скорее подсказка, чем гарантия, но вполне вероятно, что она действительно соберет мусор в моем опыте.)Я только в процессе ведения блога о разработке платформы для тестирования в .NET. У меня есть несколько из предыдущих постов , которые могут быть в состоянии дать вам некоторые идеи - не все будет уместно, конечно, но некоторые из них могут быть.
gc
всегда освобождает неиспользуемую память.
System.gc()
, как вы предлагаете минимизировать сборку мусора в одном тесте из-за объектов, созданных в предыдущих тестах? Я прагматичный, а не догматичный.
jmh является недавним дополнением к OpenJDK и написано некоторыми инженерами по производительности из Oracle. Конечно, стоит посмотреть.
JMH - это Java-система для построения, запуска и анализа нано / микро / макро тестов, написанных на Java и других языках, предназначенных для JVM.
Очень интересные фрагменты информации скрыты в примерах тестовых комментариев .
Смотрите также:
Должен ли эталон измерять время / итерацию или итерации / время и почему?
Это зависит от того, что вы пытаетесь проверить.
Если вас интересует задержка , используйте время / итерацию, а если вас интересует пропускная способность , используйте итерации / время.
Если вы пытаетесь сравнить два алгоритма, сделайте по крайней мере два теста для каждого, чередуя порядок. то есть:
for(i=1..n)
alg1();
for(i=1..n)
alg2();
for(i=1..n)
alg2();
for(i=1..n)
alg1();
Я обнаружил некоторые заметные различия (иногда 5-10%) во время выполнения одного и того же алгоритма на разных проходах.
Кроме того, убедитесь, что n очень велико, чтобы время выполнения каждого цикла составляло как минимум 10 секунд или около того. Чем больше итераций, тем значительнее показатели времени тестирования и тем надежнее данные.
Убедитесь, что вы каким-то образом используете результаты, которые вычисляются в тестируемом коде. В противном случае ваш код может быть оптимизирован.
Существует множество возможных подводных камней для написания микро-тестов в Java.
Во-первых: Вы должны рассчитывать со всеми видами событий, которые занимают более или менее случайное время: сборка мусора, эффекты кэширования (ОС для файлов и ЦП для памяти), IO и т. Д.
Второе: вы не можете доверять точности измеренного времени для очень коротких интервалов.
Третье: JVM оптимизирует ваш код во время выполнения. Так что разные прогоны в одном и том же JVM-экземпляре будут становиться все быстрее и быстрее.
Мои рекомендации: сделайте тест производительности за несколько секунд, это более надежно, чем время выполнения за миллисекунды. Прогрейте JVM (это означает, что хотя бы один раз тест будет выполнен без измерения, чтобы JVM могла выполнять оптимизацию). И проведите свой тест несколько раз (возможно, 5 раз) и возьмите среднее значение. Запустите каждый микропроцессор в новом экземпляре JVM (вызовите каждый тест нового Java), иначе эффекты оптимизации JVM могут повлиять на последующие выполняемые тесты. Не выполняйте вещи, которые не выполняются в фазе разогрева (так как это может вызвать загрузку классов и перекомпиляцию).
Следует также отметить, что также может быть важно проанализировать результаты микропроцессора при сравнении различных реализаций. Поэтому следует провести тест значимости .
Это связано с тем, что реализация A
может быть быстрее во время большинства прогонов теста, чем реализация B
. Но A
может также иметь более высокий разброс, поэтому измеренный выигрыш в производительности A
не будет иметь никакого значения по сравнению с B
.
Поэтому важно также правильно написать и запустить микро-тест, а также правильно его проанализировать.
В дополнение к другим отличным советам, я бы также помнил следующее:
Для некоторых процессоров (например, диапазон 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");
println
, а не отдельная строка заголовка или что-то, и System.nanoTime()
должен быть оценен как первый шаг в построении строкового аргумента для этого вызова. С первым компилятор не может ничего поделать со вторым, и никто даже не побуждает их делать дополнительную работу перед записью времени остановки.
http://opt.sourceforge.net/ Java Micro Benchmark - управляющие задачи, необходимые для определения сравнительных характеристик производительности компьютерной системы на разных платформах. Может использоваться для руководства решениями по оптимизации и для сравнения различных реализаций Java.