Як написати правильний мікро-орієнтир на Java?


870

Як написати (і запустити) правильний мікро-орієнтир на Java?

Я шукаю кілька зразків коду та коментарів, що ілюструють різні речі, про які варто задуматися.

Приклад: Чи повинен показник вимірювати час / ітерацію чи ітерацію / час, і чому?

Пов'язане: Чи прийнятний показник секундоміра?


Дивіться [це запитання] [1] від декількох хвилин тому для отримання деякої пов’язаної інформації. редагувати: вибачте, це не має бути відповіді. Я повинен був розмістити коментар. [1]: stackoverflow.com/questions/503877/…
Тіаго

Саме після планування направити афішу цього питання на таке питання я зазначив, що це питання не існує. Тож ось воно, сподіваємось, воно з часом збирає кілька хороших порад.
Джон Нільссон

5
Java 9 може надати деякі функції для мікро-бенчмаркінгу: openjdk.java.net/jeps/230
Raedwald

1
@Raedwald Я думаю, що цей JEP має на меті додати до JDK-код якийсь мікро-орієнтир, але я не думаю, що jmh буде включений до JDK ...
assylias

1
@Raedwald Привіт з майбутнього. Це не зробило розріз .
Майкл

Відповіді:


787

Поради щодо написання мікро-тестів від творців 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 , супорт або Білл і Павла Відмінно UCSD орієнтири для Java .


5
Це також була цікава стаття: ibm.com/developerworks/java/library/j-jtp12214
Джон Нільссон

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

5
Кілька паперів від javaOne: azulsystems.com/events/javaone_2009/session/…
bestsss

93
Слід зазначити, що System.nanoTime()не є гарантовано більш точним, ніж System.currentTimeMillis(). Це гарантується лише принаймні настільки ж точно. Однак, як правило, це значно точніше.
Гравітація

41
Основна причина, чому треба використовувати System.nanoTime()замість цього, System.currentTimeMillis()- це те, що перший гарантовано монотонно зростає. Віднімання повернених значень двох currentTimeMillisвикликів насправді може дати негативні результати, можливо, тому, що системний час було скориговано деяким демоном NTP.
Вальдхайнц

239

Я знаю, що це питання було позначене як відповідь, але я хотів би згадати дві бібліотеки, які допомагають нам написати мікро-орієнтири

Суппорт від Google

Початок роботи з навчальними посібниками

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

JMH від OpenJDK

Початок роботи з навчальними посібниками

  1. Уникнення контрольних підводних каменів у спільному проекті
  2. http://nitschinger.at/Using-JMH-for-Java-Microbenchmarking
  3. http://java-performance.info/jmh/

37
+1 це може бути додане як правило 8 прийнятої відповіді: Правило 8: оскільки так багато речей може піти не так, напевно, ви повинні використовувати наявну бібліотеку, а не намагатися зробити це самостійно!
assylias

8
@Pangea jmh, мабуть, перевершує сучасний суппорт. Дивіться також: groups.google.com/forum/#!msg/mechanical-sympathy/m4opvy4xq3U/…
assylias

86

Важливі речі для тестів Java:

  • Спершу розминіть JIT, запустивши код кілька разів перед тим, як встановити його
  • Переконайтеся, що ви запускаєте його досить довго, щоб можна було виміряти результати за секунди або (краще) десятки секунд
  • Хоча ви не можете зателефонувати System.gc()між ітераціями, добре запустити їх між тестами, щоб кожен тест, сподіваємось, отримав «чистий» простір пам’яті для роботи. (Так, gc()це скоріше натяк, ніж гарантія, але дуже ймовірно, що він справді збиратиме сміття за моїм досвідом.)
  • Мені подобається показувати ітерації та час, а кількість часу / ітерацію, яку можна масштабувати таким чином, що алгоритм "найкращого" отримує оцінку 1,0, а інші оцінюються відносно. Це означає, що ви можете довго запускати всі алгоритми, змінюючи і кількість ітерацій, і час, але все ж отримуючи порівнянні результати.

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


3
Незначний нітко: IMO "так, щоб кожен тест отримував" повинен бути ", щоб кожен тест міг отримати", оскільки перший створює враження, що виклик gc завжди звільняє невикористану пам'ять.
Санджай Т. Шарма

@ SanjayT.Sharma: Ну, намір полягає в тому, що це насправді так і є. Хоча це суворо не гарантується, насправді це досить сильний натяк. Відредагуйте, щоб було зрозуміліше.
Джон Скіт

1
Я не згоден з викликом System.gc (). Це натяк, ось і все. Навіть "сподіваємось щось зробити". Ніколи не слід її називати. Це програмування, а не мистецтво.
gyorgyabraham

13
@gyabraham: Так, це підказка - але це, як я бачив, зазвичай приймається. Отже, якщо вам не подобається використовувати System.gc(), як ви пропонуєте мінімізувати збирання сміття за один тест завдяки об’єктам, створеним у попередніх тестах? Я прагматичний, а не догматичний.
Джон Скіт

9
@gyabraham: Я не знаю, що ви маєте на увазі під "великим запасом". Чи можете ви детально розробити, чи є у вас пропозиція дати кращі результати? Я прямо сказав, що це не гарантія ...
Джон Скіт

48

jmh - це недавнє доповнення до OpenJDK, його написали деякі інженери з продуктивності Oracle. Безумовно, варто подивитися.

Jmh - це джгут Java для побудови, запуску та аналізу нано / мікро / макро-тестів, написаних на Java та інших мовах, орієнтованих на JVM.

Дуже цікаві відомості, поховані в коментарях зразків тестів .

Дивись також:


1
Дивіться також це повідомлення в блозі: psy-lob-saw.blogspot.com/2013/04/…, щоб дізнатись про початок роботи з JMH.
Nitsan Wakart

FYI, JEP 230: Microbenchmark Suite - це пропозиція OpenJDK, що базується на цьому проекті джгута Java Microbenchmark (JMH) . Не зробив розріз для Java 9, але може бути доданий пізніше.
Василь Бурк

23

Чи повинен показник вимірювати час / ітерацію або ітерації / час, і чому?

Це залежить від того, що ви намагаєтеся протестувати.

Якщо вас цікавить затримка , використовуйте час / ітерацію, а якщо вас цікавить пропускна здатність , використовуйте ітерації / час.


16

Якщо ви намагаєтеся порівняти два алгоритми, зробіть принаймні два орієнтири для кожного, чергуючи порядок. тобто:

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.
Менмент

15

Переконайтеся, що ви якось використовуєте результати, які обчислюються в орієнтованому коді. В іншому випадку ваш код може бути оптимізований далеко.


13

Існує багато можливих підводних каменів для написання мікро-орієнтирів на Java.

По-перше: вам доведеться обчислювати всілякі події, які займають час більш-менш випадкові: збір сміття, кешування ефектів (ОС для файлів і процесора для пам'яті), IO і т.д.

По-друге: Ви не можете довіряти точності вимірюваних разів протягом дуже коротких інтервалів.

По-третє: JVM оптимізує ваш код під час виконання. Так різні запуски в одному і тому ж JVM-екземплярі ставатимуть все швидше і швидше.

Мої рекомендації. Зробіть орієнтир запустити кілька секунд, це надійніше, ніж час виконання протягом мілісекунд. Прогрійте JVM (означає запустити показник хоча б один раз без вимірювання, щоб JVM міг запускати оптимізації). І запустіть свій орієнтир кілька разів (можливо, 5 разів) і візьміть середнє значення. Запустіть кожен мікро-орієнтир у новому екземплярі JVM (виклик для кожного базового тесту нового Java), інакше ефекти оптимізації JVM можуть вплинути на більш пізні запущені тести. Не виконуйте речі, які не виконуються у фазі розминки (оскільки це може викликати завантаження класів та перекомпіляцію).


8

Слід також зазначити, що може бути важливим також аналіз результатів мікро-орієнтиру при порівнянні різних реалізацій. Тому тест на значимість .

Це тому, що впровадження Aможе бути швидшим під час більшості етапів еталону, ніж впровадження B. Але Aтакож може бути більший розкид, тому вимірювана користь від продуктивності Aне матиме жодного значення в порівнянні з B.

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


8

Щоб додати до інших чудових порад, я також пам’ятаю про наступне:

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

Так, важливо не робити непов’язану роботу всередині приуроченої області, але ваш перший приклад все-таки прекрасний. Існує лише один виклик println, а не окремий рядок заголовка чи щось подібне, і System.nanoTime()його слід оцінювати як перший крок у побудові рядкового аргументу для цього виклику. Немає нічого, що компілятор може зробити з першим, що вони не можуть зробити з другим, і жоден навіть не заохочує їх робити додаткові роботи перед записом часу зупинки.
Пітер Кордес

7

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


2
Здається, це лише тестовий апарат JVM +, а не довільний фрагмент коду Java.
Стефан Л
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.