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

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

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

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

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

11 ответов


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

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

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

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

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

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

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

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

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

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

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


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

суппорт от 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/

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

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

Я просто в процессе ведения блога о разработке системы бенчмаркинга в .Сеть. У меня есть пара of ранее должности


jmh является недавним дополнением к OpenJDK и был написан некоторыми инженерами производительности из Oracle. Определенно стоит взглянуть.

jmh-это Java-жгут для создания, запуска и анализа тестов nano/micro/macro, написанных на 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 значение теста должно быть сделано.

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

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


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


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

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

возможно, более принципиально важный аспект, что у вас есть прямой контроль над: убедитесь, что вы измеряете правильно! Например, если вы используете 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");