Статьи

Общие подводные камни в бенчмаркинге

Время от времени каждый из нас пытается измерить производительность определенных частей кода. Это может быть либо полнофункциональное приложение, измеряемое по определенному количеству пользовательских транзакций в минуту, либо микробенчмарк. Который вы написали, чтобы доказать, что ваш коллега-разработчик не прав. Особенно в последнем случае полезно знать о подводных камнях, искажающих результаты тестов. Глядя на контрольные показатели, которые я создал ~ семь лет назад, я должен признать, что доказывал свою точку зрения в нескольких случаях, которые я действительно ошибался. Мои извинения, дорогие друзья. Но я кое-чему научился и думаю, что стоит поделиться. Чтоб ты не попал в ту же глупую ситуацию.

Подводный камень 1: шумные соседи.

Запуская эталонный тест на своем ПК, вы не запускаете DVD-RIP рядом с ним. Но как насчет торрентов, которые вы забыли на заднем плане? Или винда тянет на следующее обновление? Подобные проблемы несколько раз искажали результаты моего теста. Pitfall

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

Подводный камень 2: паузы для сбора мусора.

Измерение производительности в среде сбора мусора также должно учитывать паузы сбора мусора. Итак, всякий раз, когда вы запускаете тест, рекомендуется включить ведение журнала сборщика мусора через -XX: + PrintGCDetails. Теперь при мониторинге логов:

1
2
3
4
0.415: [GC 0.415: [ParNew: 19136K->2112K(19136K), 0.0255607 secs] 100164K->100154K(118108K), 0.0256139 secs] [Times: user=0.04 sys=0.01, real=0.03 secs]
0.451: [GC 0.451: [ParNew: 19136K->19136K(19136K), 0.0000286 secs]0.451: [CMS0.461: [CMS-concurrent-mark: 0.033/0.099 secs] [Times: user=0.12 sys=0.04, real=0.10 secs]
0.580: [Full GC 0.580: [CMS: 107774K->107774K(107776K), 0.0553739 secs] 126910K->126910K(126912K), [CMS Perm : 4620K->4620K(21248K)], 0.0554400 secs] [Times: user=0.05 sys=0.00, real=0.05 secs]
0.635: [Full GC 0.635: [CMS: 107774K->107775K(107776K), 0.0919800 secs] 126910K->126872K(126912K), [CMS Perm : 4620K->4609K(21248K)], 0.0920382 secs] [Times: user=0.09 sys=0.00, real=0.09 secs]

Вы можете оценить влияние на ваш тест. Как видно из приведенного выше примера, программа работала всего 635 мс, и за это время мы потратили 270 мс (42,5%), ожидая, пока GC выполнит свою работу. Теперь я не говорю, что ваши тесты должны быть настроены так, чтобы избежать пауз в GC. Я просто предупреждаю, что в случаях, когда вы тестируете, например, поведение больших структур данных, вы должны следить за тем, чтобы ваши тесты не становились GC-привязанными в какой-то момент.

Подводный камень 3: сборник Just In Time.

Современные JVM удивительны в том смысле, что JVM оптимизирует код во время выполнения. Таким образом, в большинстве случаев вы не хотели бы делать поспешные выводы, связанные с производительностью, прежде чем JIT-компиляция закончит свою работу. Чтобы знать, что JIT делает во время теста, вам нужно добавить -XX: + PrintCompilation к вашим аргументам JVM и отслеживать журналы:

1
2
3
4
5
240       org.codehaus.groovy.reflection.CachedMethod::compareToMethod (125 bytes)
241       java.util.zip.ZipFile::access$300 (5 bytes)
242       java.util.ArrayList::indexOf (67 bytes)
243       java.util.Arrays::fill (28 bytes)
72        made zombie  sun.misc.URLClassPath::getLoader (136 bytes)

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

Ловушка 4: Загрузчики классов.

Помните об эффектах загрузки классов. JVM — ленивый ублюдок, и он не будет загружать классы, пока они ему действительно не понадобятся. Для эталонных тестов это означает, что вы не должны включать свои измерения до завершения загрузки классов. С микробенчмарками это часто не так уж и сложно — разогрев — это все, что вам нужно. Но при тестировании в реальном мире вы можете включить -verbose: class flag, чтобы проверить, все ли классы были загружены до того, как ваши измерения были включены.

Подводный камень 5: Машинная архитектура.

Если вы тестируете что-то, что планируете использовать в производственном коде, вы должны запустить тест на той же архитектуре, которую использует ваша производственная среда. В противном случае вы можете получить полностью искаженные результаты — из-за разницы в 32/64 разряда, разности алгоритма GC, конфигураций области кучи, количества ядер и т. Д.

Подводный камень 6: Измерение.

Во-первых, это единица измерения. Если ваши результаты измеряются в наносекундах, скорее всего, вы находитесь не на той территории. Постарайтесь создать более грубые тесты, чтобы вы могли, по крайней мере, использовать миллисекунды. А еще лучше — через десятки секунд. Измерение действительно небольших единиц работы может снова исказить результаты — само измерение может слишком сильно изменить результат. Второе — отмерить несколько раз. На самом деле, десятки раз, если вы можете. И я не имею в виду, что вы итерируете 30x по сравнению с эталонным кодом в вашем коде. Я имею в виду, что вы запускаете весь тест 30 раз и суммируете результаты.

Теперь, если все здесь напугало вас от написания микробенчмарок — хорошо. Потому что чаще всего ваши микробенчмарки не помогают вам с реальными проблемами производительности вашего приложения. Просто напишите простой, понятный код, который избегает умных оптимизаций. Это код, который JIT настоящего и будущего могут оптимизировать сами. И это работа, которая действительно должна быть их, а не вашей. Но если помимо того, что вы напуганы, вам также стало любопытно — у нас есть еще много интересного контента, которым можно поделиться. Просто следуйте за нами в Twitter или RSS и будьте уведомлены вовремя.

Ссылка: Типичные ошибки в тестировании производительности от нашего партнера по JCG Иво Маги в блоге Plumbr Blog .