Ответ 1
К сожалению, пробоотборники выборки довольно ограничены, когда дело доходит до углубленного профилирования по ряду причин:
-
Пробоотборники ограничены периодом выборки: например, VisualVM в настоящее время имеет минимальный период выборки 20 мс. Современные процессоры могут выполнять несколько миллионов команд за это время - конечно, более чем достаточно, чтобы называть несколько коротких методов и возвращаться от них.
Хотя очевидным решением было бы сократить период выборки, это также увеличило бы влияние профилировщика на ваше приложение, представляя приятный пример неопределенности принцип.
-
Пробоотборники легко путаются встроенным кодом: как JVM, так и любой достойный компилятор будут выстраивать тривиальные и/или часто называемые методы, тем самым включая их код в код своего вызывающего. Профилировщики пробоотбора не имеют возможности сказать, какие части каждого метода действительно принадлежат ему и которые принадлежат к встроенным вызовам.
В случае VisualVM Self time фактически включает время выполнения как метода, так и любого встроенного кода.
-
Сэмплеры могут запутаться с помощью продвинутой виртуальной машины: например, в современных реализациях JVM методы не имеют стабильного представления. Представьте себе, например, следующий метод:
void A() { ... B(); ... }
Когда запуск JVM
B()
интерпретируется прямо из байт-кода, тем самым занимает довольно много времени, что делает его видимым для сэмплера. Затем через некоторое время JVM решает, чтоB()
является хорошим кандидатом для оптимизации и компилирует его в собственный код, что значительно ускоряет его. И еще через некоторое время JVM может решить включить вызовB()
, включив его код вA()
.В лучшем случае профилировщик выборки покажет стоимость этих первых прогонов, а затем стоимость любых последующих прогонов будет включена в время, потраченное вызывающим абонентом. Это, к сожалению, может запутать неопытного разработчика в недооценке стоимости метода, который был встроен.
В худшем случае эта стоимость может быть назначена на вызов сестры, а не на вызывающего. Например, я в настоящее время профилирую приложение с помощью VisualVM, где точка доступа кажется методом
ArrayList.size()
. В моей реализации Java этот метод является простым полевым getter, что любая JVM должна быстро встраиваться. Тем не менее, профайлер показывает это как основного потребителя времени, полностью игнорируя кучу соседних вызововHashMap
, которые, очевидно, намного дороже.
Единственный способ избежать этих недостатков - использовать инструментальный профилировщик, а не выборку. Инструментальные профилировщики, например, такие, которые предусмотрены на вкладке Profiler в VisualVM, по существу записывают каждый вход и выход метода в выбранном коде. К сожалению, инструментальные профилировщики довольно сильно влияют на профилированный код:
-
Они вставляют свой контрольный код вокруг каждого метода, который полностью изменяет способ обработки метода JVM. Даже простые методы getter/setter field не могут быть больше привязаны из-за дополнительного кода, что искажает любые результаты. Профайлер обычно пытается объяснить эти изменения, но это не всегда успешно.
-
Они вызывают массовые замедления для профилированного кода, что делает их совершенно непригодными для мониторинга полных приложений.
По этим причинам инструментальные профилирующие устройства в основном подходят для анализа горячих точек, которые уже были обнаружены с использованием другого метода, такого как профилировщик выборки. Используя только выбранный набор классов и/или методов, можно ограничить побочные эффекты профилирования конкретными частями приложения.