2009-02-02 11 views
656

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

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

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

Похожие: Is stopwatch benchmarking acceptable?

+0

См. [Этот вопрос] [1] из нескольких минут назад для получения некоторой информации. Редактировать: извините, это не должно быть ответом. Я должен был разместить комментарий. [1]: http://stackoverflow.com/questions/503877/how-can-i-measure-time-with-microsecond-precision-in-java – Tiago

+0

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

+4

Java 9 может предоставить некоторые функции для микро-бенчмаркинга: http://openjdk.java.net/jeps/230 – Raedwald

ответ

609

Советы о написании микро контрольных показателей from the creators of Java HotSpot:

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

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

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

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

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

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

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

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

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

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

+4

Это была также интересная статья: http://www.ibm.com/developerworks/java/library/j-jtp12214/ –

+103

Кроме того, никогда не используйте System.currentTimeMillis(), если вы не в порядке с точностью + или - 15 мс, что характерно для большинства комбинаций ОС + JVM. Вместо этого используйте System.nanoTime(). –

+3

Некоторая бумага из javaOne: http://www.azulsystems.com/events/javaone_2009/session/2009_J1_Benchmark.pdf – bestsss

64

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

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

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

+3

Незначительный nitpick: IMO ", так что каждый тест получает" должно быть ", чтобы каждый тест мог получиться", так как первый создает впечатление, что вызов 'gc' * always * высвобождает неиспользованную память. –

+0

@ SanjayT.Sharma: Ну, * намерение * заключается в том, что оно на самом деле.Хотя это не строго гарантировано, это на самом деле довольно сильный намек. Будет редактировать, чтобы быть более четким. –

+1

Я не согласен с вызовом System.gc(). Это намек, вот и все. Даже «он, надеюсь, что-то сделает». Вы никогда не должны его называть. Это программирование, а не искусство. – gyorgyabraham

10

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

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

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

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

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

11

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

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

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

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

+5

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

13

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

16

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

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

5

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

+1

Кажется, нужно просто сравнить оборудование JVM +, а не произвольную часть кода Java. –

193

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

Caliper from Google

Getting Started учебники

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

JMH from OpenJDK

Getting Started учебники

  1. Avoiding Benchmarking Pitfalls on the JVM
  2. http://nitschinger.at/Using-JMH-for-Java-Microbenchmarking
  3. http://java-performance.info/jmh/
+30

+1 он мог быть добавлен как Правило 8 принятого ответа: Правило 8: потому что так много вещей может пойти не так, вы, вероятно, должны использовать существующую библиотеку, а не пытаться сделать это сами! – assylias

+4

@ Pangea jmh, вероятно, превосходит сегодня сумочку, см. Также: https://groups.google.com/forum/#!msg/mechanical-sympathy/m4opvy4xq3U/7lY8x8SvHgwJ – assylias

6

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

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

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

36

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

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

Очень интересные фрагменты информации, погребенные в the sample tests comments.

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

+1

Смотрите также эту запись в блоге: http: // psy- lob-saw.blogspot.com/2013/04/writing-java-micro-benchmarks-with-jmh.html для получения более подробной информации о начале работы с JMH. –

+0

FYI, [JEP 230: Microbenchmark Suite] (http://openjdk.java.net/jeps/230) - это предложение [OpenJDK] (http://openjdk.java.net/) на основе этого [Java Microbenchmark Harness (JMH)] (http://openjdk.java.net/projects/code-tools/jmh/). [Не делал разреза для Java 9] (http://mail.openjdk.java.net/pipermail/jdk9-dev/2016-March/003894.html), но может быть добавлен позже. –

3

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

Для некоторых процессоров (например, диапазон 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");