Пользователь
0,0
рейтинг
8 мая 2012 в 15:22

Разработка → Do It Yourself Java Profiling

Java*
На прошедшей конференции Appication Developer Days, Роман Елизаров (elizarov) рассказал, как профилировать, т.е. исследовать производительность любых Java-приложений, без использования специализированных инструментов, будь они хоть вендорские, хоть open-source.Оказывается, можно использовать малоизвестные, встроенные в JVM возможности (threaddumps, java agents, bytecode manipulation), и быстро и эффективно реализовать профилирование, которое можно запускать постоянно даже на боевой системе.Вот видео доклада (тут оно косолапо эмбедится, но оно 1280x720, все отлично читаемо):

Но я предлагаю также, взглянуть на 70K текста иллюстрированной статьи-стенограммы под катом, составленной мной по видео и слайдам.
DIY Java Profiling (Роман Елизаров, ADD-2011).pdf У меня сегодня будет доклад про «Сделай себе сам профилирование на Яве». Слайды будут на английском, доклад я буду делать на русском. Слайдов очень-очень много, а времени не так много, поэтому я буду какие-то очень быстро проскакивать, и постараюсь оставить больше времени в конце на вопросы, и может быть даже где-то в середине. В принципе, не стесняйтесь, если захочется вдруг что-то спросить или уточнить, и даже меня прервать. Я лучше подробнее освещу то, что вам интересно, чем буду просто рассказывать то, что я хотел рассказать.

Доклад основан на реальном опыте, мы в компании больше десяти лет занимаемся созданием сложных, очень высоконагруженных финансовых приложений, работающих с большими массивами данных, с миллионами котировок в секунду, c десятками тысяч пользователей онлайн, и там, при такой работе, всегда речь идет о профилировании приложения. DIY Java Profiling (Роман Елизаров, ADD-2011).pdf Профилирование приложения — неизбежный компонент любой оптимизации, оптимизация без профилирования невозможна. Вы профилируете, находите узкие места, оптимизируете-профилируете, это постоянный цикл. DIY Java Profiling (Роман Елизаров, ADD-2011).pdf Почему доклад называется «Do It Yourself Java Profiler», зачем что-то делать самому? Есть же огромное количество готовых инструментов, которые помогают профилировать — собственно профилировщики, и подобные инструменты.

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

Мы, когда пишем финансовые приложения, у нас есть еще задача обеспечить надежность системы. И мы делаем не «банки», где главное — не потерять ваши деньги, но которые могут быть часами недоступны. Мы делаем брокерские системы онлайн-торговли, где постоянно 24×7 доступность систем, это одна из ключевых их качеств, они никогда не должны падать.

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

Но и инструменты частенько непрозрачны. Да, есть документация, которая описывает «что», но она не описывает, как именно он достает эти результаты, и не всегда можно понять, что он собственно намерял.

Причем даже если инструмент с открытым кодом, это ничего не меняет, потому что этого кода много, вы убъете кучу времени в нем разбираясь. Инструменты надо учиться, а делать что-то самому, конечно, намного приятней. DIY Java Profiling (Роман Елизаров, ADD-2011).pdf В чем проблема с изучением? Естественно, если вы используете какой-то инструмент часто, то стоит его учить. Если вы программируете каждый день в какой-то любимой вами среде разработки, вы ее знаете вдоль и поперек, и это знание, естественно, окупается вам сторицей.

А если вам нужно что-то сделать раз в месяц, например, раз в месяц вам нужно ради бага с производительностью заниматься профилированием, то не факт, что изучение соответствующего инструмента окупится. Конечно же, если нет ситуации, когда инструмент решает задачу в разы быстрее.

Делая что-то своими руками, вы можете переиспользовать ваши знания. Например, у вас есть знания языков программирования, своих инструментов, вы можете их углубить, расширить, уточнить, глубже изучив те инструменты, которые у вас уже есть, вместо изучения нового. DIY Java Profiling (Роман Елизаров, ADD-2011).pdf Почему доклад о Java? Ну мало того, что наша компания программирует на Java, это ведущий язык 2001 года по индексу TIOBE, отлично подходит для enterprise приложений. А для данной конкретной лекции вообще замечательно — потому что Java — это managed язык, работает в виртуальной машине, и именно профилирование в Java делается очень легко. DIY Java Profiling (Роман Елизаров, ADD-2011).pdf Я буду рассказывать во-первых, просто о том, как решить многие проблемы профилирования, написав некий код на Java. Скажу о возможностях Java-машин, которые можно использовать, и расскажу о такой технике, как манипулирование байт-кодом. DIY Java Profiling (Роман Елизаров, ADD-2011).pdf Мы посмотрим сегодня на профилирование как CPU, так и памяти. Я расскажду про разные техники. DIY Java Profiling (Роман Елизаров, ADD-2011).pdf Профилирование CPU. Самый простой способ, это просто взять и запрограммировать. Если вам нужно разобраться, куда, сколько, чего, в вашей программе занимает времени, и сколько раз вызывается, то самый-самый простой способ: не надо никаких инструментов, ничего — написать просто несколько строчек кода.

В Java есть замечательная функция «currentTimeMillis», которая возвращает текущее время. Можно ее замерять в одном месте, замерять в другом, а дальше можно посчитать, сколько раз это сделано, суммарное время, минимальное и максимальное время, все что угодно. Самый такой простой способ. DIY в своей максимальной простоте и примитивизме.

Как ни странно, на практике способ отлично работает, приносит кучу пользы — потому что быстр, удобен и эффективен. DIY Java Profiling (Роман Елизаров, ADD-2011).pdf Когда этот метод хорошо работает? Это замечательно работает для бизнес-методов — бизнес-метод большой, вызывается не очень часто, и вам нужно что-то про него измерить. Более того, написав этот код, раз уж вы его написали — он становится частью вашего приложения и частью функционала. Более менее любое современное большое приложение содержит интерфейсы управления, какие-то статистики, … и в общем производительность приложения — это одна из трех вещей, которую часто хочется видеть, чтобы приложение о себе выдавало, просто как часть своего функционала.

В этом смысле, запрограммировать приложение, чтобы оно само себя профилировало, является логичным шагом. Вы увеличиваете функционал приложения, профилирование приложения становится частью его функционала. Особенно, если таким образом вы расставляете определения в ваших бизнес-методах, которых конечный пользователь вызывает, то конечному пользователю эта информация тоже будет осмысленна. Сколько раз и какие методы вызывались, сколько по времени отрабатывали и так далее. Информация, которую вы собираете, в данном случае, при данном подходе, полностью под вашим контролем. Можете замерять количество вызовов, минимальное время, максимальное время, среднее считать, можете строить гистограммы распределения времени выполнения, считать медианы и персентили. Можете разные пути исполнения в коде отслеживать по разному, как в этом примере, кто успел разглядеть, пока я говорил, обратил внимание, что в зависимости от пути исполнения, мы записываем разную статистику: как часто результат запроса попал в кеш, и сколько времени это заняло, и как часто запросу пришлось лезть в базу данных, и сколько времени это заняло. DIY Java Profiling (Роман Елизаров, ADD-2011).pdf Это возможно, если вы этот код пишете сами, собираете статистику, встраиваете сами в свое приложение. DIY Java Profiling (Роман Елизаров, ADD-2011).pdf Более того, вам, как человеку, который занимается циклом профилирования-оптимизации, вы потом эту информацию используете — что же в вашем приложении происходит? Эта информация всегда находится внутри вашего приложения, код работает в живой системе. У вас произошел какой-то неудачный день, что-то система не так работала, вы можете посмотреть в логах эту статистику, разобраться, и так далее.

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

Дело в том, что такой прямой метод уже не подойдет, потому что метод «currentTimeMillis» не быстрый, и меряет только в миллисекундах. DIY Java Profiling (Роман Елизаров, ADD-2011).pdf Если вам нужно замерить только количество вызовов, то достаточно быстро можно это сделать используя Java-класс «AtomicLong». С его помощью, вы можете, внося минимальный вклад в производительность, посчитать количество вызовов какого-нибудь интересующего вас метода. Это будет работать до десятков тысяч вызов в секунду, не сильно искажая работу самого приложения. DIY Java Profiling (Роман Елизаров, ADD-2011).pdf Что же делать, если вам нужно еще замерить время выполнения? Замер времени исполнения коротких методов — это очень сложная тема. Стандартными никакими методами она не решается, несмотря на то, что в Java есть метод «systemNanoTime», он эти проблемы не решает, он сам по себе медленный, и с помощью него что-то быстрое сложно замерить.

Единственный реальный выход — это использовать нативный код, для x86 процессора есть такая замечательная инструкция rdtsc, которая возращает счетчик количества тактов процессора. Напрямую к ней доступа нет, можно написать на C однострочный метод, который вызывает «rdtsc», а дальше слинковать его с Java-кодом, и вызывать из Java. Этот вызов вам займет сто тактов, и если вам нужно замерить кусок кода, который занимают тысячу-другую тактов, то это осмысленно, если у вас идет оптимизация каждого машинного такта, и вы хотите понять, «плюс-минус», «быстрее-медленнее», как вы работаете. Это действительно редкий случай, когда вам нужно оптимизировать каждый такт.

Чаще всего, когда речь идет о каких-то более коротких кусках кода, и более часто вызываемых, используют другой подход, который называется «семплирование». Вместо точных замеров, сколько раз и чего вызывается, вы периодически анализируете выполнение программы, смотрите, где она исполняется, в произвольные моменты времени, например — раз в секунду, или раз в десять секунд. DIY Java Profiling (Роман Елизаров, ADD-2011).pdf Смотрите, где происходит исполнение, и считаете, в каких местах вы застаете свою программу часто. Если у вас в программе есть строчка, в которой она тратит все, или по крайней мере 90% своего времени, например, какой-нибудь цикл, а там, в глубине, какая-то строчка, то скорее всего, при остановке исполнения вы ее в этой строчке и застанете.

Такое место в программе называется «горячей точкой». Это всегда замечательный кандидат для оптимизации. Что классно — есть встроенная фукнция под названием «thread dump», чтобы получить дамп всех потоков. В Windows она делается путем нажатия CTRL-Break на консоли, а на Linux и других юниксах это делается посылкой третьего сигнала, командой «kill -3». DIY Java Profiling (Роман Елизаров, ADD-2011).pdf В этом случае, Java-машина на консоль выводит подробное описание состояния всех потоков. И если у вас действительно есть горячее место в коде, то скорее всего программу там и застанете. Поэтому опять же, когда у вас проблема производительности с кодом, не надо бежать к профилировщику, не надо ничего делать. Видите, что тормозит, сделайте хоть один thread dump, и посмотрите. Если у вас одно горячее место, в котором программа тратит все время, вы в thread dump и увидите эту строчку, в своей любимой среде разработки, без использования каких-нибудь сторонних, дополнительных инструментов. Посмотрите этот код, изучите, оптимизируйте — либо он слишком часто вызывается, либо он медленно работает, дальше уже разбор полетов, оптимизация, либо дальнейшее профилирование. DIY Java Profiling (Роман Елизаров, ADD-2011).pdf Также в современных Java-машинах есть замечательная утилита «jstack», ей можно передать идентификатор процесса, и получить на выходе thread dump.

Сделайте не один thread dump, сделайте несколько thread dumpов. Если первый ничего не выловит, посмотрите еще на пару-тройку. Может у вас в горячей точке не сто процентов времени программа проводит, а 50%. Сделав несколько thread dumpов, вы явно в хоть какой-то из этих моментов, достанете ваш код из горячей точки, и глазками посмотрите те места, где вы застали ваш код. DIY Java Profiling (Роман Елизаров, ADD-2011).pdf Эту идею можно развить дальше. Можно, запустив ява-машину, перенаправить ее выход в файл, и запустить скрипт, который делает thread dump каждые три секунды. Это можно делать совершенно спокойно на живой системе, без какого либо риска что-то с ней сделать. Потому что сам сбор thread dump досточно быстрая процедура, занимает от силы 100 миллисекунд, даже при очень большом количестве тредов.

А если вы пишите на яве, то скорее всего ваша система не hard real time, и вам там наносекунды не важны, потому что у вас уже периодически встречается сборка мусора и так далее. Т.е. вам лишнее засыпание системы на сто миллисекунд катастрофы не создает.

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

Поэтому можно не переживать о лишних ста миллисекундах, и раз в три секунды делать thread dump, можно совершенно безопасно, даже на живой системе. При этом thread dump, это часть java-машины, хорошо оттестированная — я за весь свой опыт работы ни разу не видел, чтобы попытка сделать на Java-машине thread dump сделала с ней что-то плохое.

Т.е. это совершенно безопасный инструмент профилирования живых и работающих систем. После этого, получив файл thread dump, его можно посмотреть глазками, а можно написать несложный кусок кода, который анализирует, считает какие-нибудь статистики → хотя бы тупо, посмотреть, какие методы проявились и сколько раз, посмотреть, в каком состоянии находились потоки. DIY Java Profiling (Роман Елизаров, ADD-2011).pdf При этом если стандартные инструменты профилирования, действительно смотрят на состояние потока, которое выводит Java-машина («RUNNABLE»), то в реальности, ваше состояние ни о чем не говорит, потому что если ваша программа много работает с базой данных, и много работает с какими-то внешними… сетью, то ваш код может ожидать получение данных по сети, при этом java-машина его считает «RUNNABLE», и вы ничего не сможете понять — какие у вас реально методы, и какие ждут данных от сети. С другой стороны, если вы сами анализируете стеки, — вы можете написать, вы-то знаете, что ваша программа делает, что вот это — вызов в базу данных, что этот метод в стеке означает, что вы вошли в базу данных, можете посчитать, сколько процентов времени вы проводите в базе данных, и так далее, тому подобное. Вы можете знать, что вот этот метод на самом деле CPU не жрет, хотя java-машина думает, что он «RUNNABLE». DIY Java Profiling (Роман Елизаров, ADD-2011).pdf Более того, thread dump можно интегрировать в приложения, в Java есть замечательный метод
Thread.getAllStackTraces 
который позволяет получить информацию о stacktrace программно.

Таким образом, вы можете интегрировать профилирование, как функциональную часть этого приложения, и распространять приложение вашим клиентам, с уже встроенным профилированием. Тем самым, у вас будет постоянный поток информации, который вы сможете анализировать для улучшения вашего приложения. DIY Java Profiling (Роман Елизаров, ADD-2011).pdf Но есть проблема. Дело в том, что когда вы Java-машину просите сделать thread-dump, она не просто процесс останавливает, и делает стек, она включает флажок, что ява-машине надо бы остановится в следующем безопасном месте. «Безопасное место» — это специальные места, которые расставляет компилятор по коду, где у программы есть определенное состояние, где понятно, что у нее в регистрах, понятна точка исполнения, и так далее. Если брать кусок последовательного кода, где нет никаких обратных переходов, никаких циклов, то там «safe point» может не оказаться вообще. Причем неважно, что вызовы методов могут заинлайнится hotspot-ом, и savepoint-ов тоже не будет.

Поэтому если вы видите в thread dumpe какую-то строчку — это совершенно не значит, что это горячая строчка вашего кода, это просто ближайший savepoint к горячей точке. Потому что когда вы нажимаете «CTRL-BREAK», Java всем потокам выставляет флажок «остановиться на ближайшем savepointе», и только когда они останавливаются, Java-машина анализирует, в каком состоянии они это делают. DIY Java Profiling (Роман Елизаров, ADD-2011).pdf Теперь перейдем к профилированию памяти, как это делается.

Во-первых, есть замечательные, готовые фичи Java-машина. Есть отличный инструмент jmap, который выводит гистограмму того, чем у вас забита память, какие объекты и сколько памяти занимают. Это замечательный инструмент для общего обзора, что же у вас происходит, и чем забита память.

Опять же, если вы никогда не профилировали программу, то чаще всего вы сразу найдете проблемы, и у вас будет пища для дальнейшей оптимизации вашего использования памяти. DIY Java Profiling (Роман Елизаров, ADD-2011).pdf Проблема в том, что вы таким образом получите информацию о всех объектах, даже тех, которые сейчас в данный момент не используются, находятся в мусоре.

Поэтому у jmap есть специальная опция «live», которая перед тем, как сделать гистограмму делает сборку мусора, оставляет только используемые объекты, и только после этого строит гистограмму.

Проблема, что уже с этой опцией, большую, живую систему работающую с многими гигабайтами памяти, запрофилировать нельзя, потому что сборка мусора системы, работающей с десятком гигабайт памяти занимает десяток-другой секунд, и это может быть неприемлимо… в любой системе, если ваша система работает с конечными людьми, человек, которому система дольше трех секунд не отвечает, считает, что она зависла. Нельзя живую, работающую с человеком систему останавливать дольше чем на секунду, на самом деле. Даже секунда уже будет человеку заметна, но еще не катастрофа, но если вы подключите какой-то инструмент, который остановит на 10 секунд — то это будет катастрофа. Поэтому часто приходится довольствоваться на живых системах jmap-ов тех объектов, которые есть, и в целом, неважно, мусор это или нет.
DIY Java Profiling (Роман Елизаров, ADD-2011).pdf Также дополнительно полезно знать дополнительные опции Java-машин. Например, у Java-машин можно попросить отпечатать гистограмму классов, когда вы делаете thread dump → «PrintClassHistogram».

Ява-машину можно попросить при исчерпании памяти записывать свое состояние на диск. Это очень полезно, потому что обычно, люди начинают оптимизировать потребление памяти только тогда, когда она заканчивается почему-то. Никто не занимается профилированием, когда все хорошо, а вот когда программе начинает памяти не хватать, она вылетает, начинают думать, как бы так соптимизировать. Поэтому это опцию полезно всегда иметь включенной. Тогда в плохом случае Java-машина вам запишет бинарный дамп, который вы можете потом, не на живой системе, любыми инструментами проанализировать. При этом, этот дамп можно в любой момент взять с Java-машины, тем же jmap-ом, с опцией «-dump», но это, опять же, останавливает Java-машину на долгое время, на живой системе вы это вряд ли заходите делать.

Реплика из зала: Там есть свойство, что этот «HeapDumpOutOfMemory» оптимизирован для тех случаев, когда память уже кончилась.

Да, конечно. «HeapDumpOutOfMemory» очень полезная опция, причем хотя она «-XX», не надо этих опций бояться, хотя этот «XX» подчеркивает их мегаспециальность, но это не экспериментальные опции, это нормальные production-опции ява-машины, они стабильные, надежные, их можно использовать на живой, реальной системе.

Это не экспериментальные опции! В java-машине есть четкое деление, но деление на экспериментальные и неэкспериментальные опции не зависит от числа X-ов.

Реплика из зала: Эта опция иногда не откладывают дампы…

Ну, в java-машине тоже бывают баги, все зависит от того, … бывают разные причины исчерпания памяти, я не буду на этом останавливаться, у нас времени мало. DIY Java Profiling (Роман Елизаров, ADD-2011).pdf Я хочу остановится на очень важном моменте, в оставшееся время, а именно, на профилировании выделения памяти.

Одно дело — чем память занята, как вы ее вообще используете. Если у вас где-то в коде есть излишнее выделение временной памяти, т.е. вы выделяете, … что-то с ним делаете, этом методе и потом забываете, он уходит в мусор, и garbage collector потом его забирает. И так вы делаете снова и снова, ваша программа будет работать медленнее, чем бы она работала с этим … но вы никаким профилировщиком по CPU это место кода не найдете, потому что сама операция выделения в Java-машине памяти, работает фантастически быстро, быстрее, чем в любом не-managed языке, C/C++, потому что в Java выделение памяти это банально увеличение одного указателя. Все. Это несколько ассемблерных инструкций, все очень быстро происходит. Она уже предварительно обнулена, все уже выделено и подготовлено. Вы этого времени при анализе горячих точек вашего кода не найдете — оно не высветится у вас никогда, ни в каком thread dumpe, ни в каком профилировщике, что это у вас горячая точка. Хотя у вас это все будет жрать ваше время, работы вашего приложения — почему? Потому что потом, сборщик мусора будет тратить время, чтобы этот мусор собрать. Поэтому смотрите, на то, сколько процентов времени ваше приложение тратит на сборку мусора.

Это полезная опция «-verbose:gc», «+PrintGC», «+PrintGCDetails», которые позволят вам разобраться, сколько времени вашего приложения уходит на сборку мусора. Если вы видите, что на сборку мусора уходит существенный процент времени, значит вы где-то в программе много выделяете памяти, вы это место не найдете, нужно искать, кто выделяет память. DIY Java Profiling (Роман Елизаров, ADD-2011).pdf Как искать? Есть встроенный в Java-машину способ, ключик «-Xaprof». Он вам, к сожалению, только при завершении процесса, выводит так называемый allocation profile, который говорит не о содержимом памяти, а о статистике выделяемых объектов → какие объекты и как часто выделялись.

Если у вас это действительно часто происходит, вы скорее всего увидите, что где-то заведен какой-то временный класс, который действительно очень часто выделяется. Попробуйте сразу сделать «aprof» — может вы сразу найдете вашу проблему. DIY Java Profiling (Роман Елизаров, ADD-2011).pdf Но не факт. Может получится ситуация, что вы увидите выделения большого количества массивов character-ов, каких-то string-ов, или чего-нибудь, и непонятно где.

Понятно, что у вас могут быть подозрения — где. Может какое-нибудь недавнее изменение, это могло внести. В конце-концов, вы можете добавить в том месте, где выделяется память слишком часто, используя ту же технику изменерения кода в atomiс longах, посчитайте, сколько раз в этом месте происходит выделение — посмотрите статистику, подозрительные места вы сами сможете проинициировать и найти.

А что делать, если у вас нет идеи, где это происходит? Ну надо как-то добавить сбор статистики всюду, по всем местам, где выделяется память. Для такого рода задач отлично подходит аспектно-ориентированное программирование, либо прямое использование манипуляций байт-кодом.

Я сейчас за оставшееся время попробую остановится как раз на технике манипуляции байт-кодом, которая как раз подходит, для решения задач типа «вот я хочу во всех местах, где выделяется массив, посчитать, сколько раз в этом месте это происходит, по всему своему коду, чтобы найти то самое место, в котором я почему-то очень много массивов int-ов выделяю». Т.е. я вижу, что их много выделяется, но я просто хочу найти где. DIY Java Profiling (Роман Елизаров, ADD-2011).pdf Манипулирование байткодом позволяет не только эти задачи решить, она позволяет любые изменения, нефункциональные, в код вносить, уже после его компиляции. Тем самым, этот способ декомпозировать профилирование от бизнес-логики. Если в начале я говорил, что часто профилирование может быть логичной частью вашего функционала, то бывают случаи, когда это не нужно, когда вам нужно найти проблему, решить, и чтобы никаких строк не осталось. В этом случае подходит такая замечательная техника, как манипулирование байт-кодом.

Причем это можно делать как с посткомпиляцией выкладки кода, так и на лету, с кодом. DIY Java Profiling (Роман Елизаров, ADD-2011).pdf Самый лучший способ, который я знаю, это использовать библиотечку ASM, ObjectWebовскую. Это open-source библиотека, которая позволяет очень легко заниматься манипуляцией байткодом, и она фантастически быстрая — можно манипулировать кодом на лету, не сильно замедляя время загрузки приложения.

ASM устроен очень просто. У него есть класс под названием class-reader, который читает .class-файлы, и преобразовывает байтики, используя шаблон Visitor, в набор вызовов вида «я вижу метод», «я вижу поле с такими-то полями в этом классе» и так далее. Когда он видит метод, он начинает с помощью «MethodVisitor» сообщать, какой он там видит байт-код.

А потом есть, с другой стороны, такая штука, как «ClassWriter», который наоборот, превращает класс в массив байтиков, который нужен ява-машине. DIY Java Profiling (Роман Елизаров, ADD-2011).pdf Чтобы например, с помощью ASM-а, отследить все выделения массивов… в-общем, это делается примитивно. Вам нужно всего парочку классов сделать. Вам нужно определить свой класс-адаптер, который, когда ему говорят, что виден метод, перекрывает, и возвращает свой собственный метод-visitor, чтобы оузнать, что в этом методе происходит.

А когда внутри метода ему говорят, что вот, есть целочисленная инструкция с байт-кодом выделения массива («NEWARRAY»), то в этот момент, он имеет возможность … вставить свои какие-нибудь байткоды в восходящий поток и все. И вы отследили все места, где у вас выделяются массивы, и поменяли соответствующий байт-код. DIY Java Profiling (Роман Елизаров, ADD-2011).pdf Дальше — что делать, если вы хотите эти изменения делать на лету.

Если у вас есть набор скомпилированных классов — то это легко → вы как бы обработали этим инструментом, и все.

Если вам это нужно делать на лету, то в java-машине есть замечательная опция — javaagent. Вы делаете специальный jar-файл, у которого в манифесте указываете опцию «Premain-Class», и указываете там имя своего класса. Потом … метод «premain» по определенному шаблону, и тем самым, вы еще до запуска основного кода, c main-методом, получаете управление, и получаете указатель на interface instrumentation. Этот интерфейс замечательный, он позволяет вам, на лету, менять классы в java-машине. Он позволяет вам поставить свой собственный class-file трансформер, который Java-машина будет вызывать для каждой попытки загрузки класса.

И вы сможете классы подменять. Т.е. загружать не только те классы, которые на самом деле лежат, а с помощью того же ObjectWebASM, анализировать что-то, менять, и подменять их на лету… можете узнать размер выделенного объекта.

Замечательный инструмент такого профилирования на коленке, когда у вас есть конкретная задача, которую нужно решить. DIY Java Profiling (Роман Елизаров, ADD-2011).pdf В завершении скажу, что совершенно не обязательно, для решения каких-то задач профилирования владеть каким-нибудь инструментом, достаточно знать байт-код, знать опции ява-машины, и знать стандатные ява-библиотеки, тот же javalang-инструмент. Это позволяет решить огромное число специфичных проблем, с которыми вы сталкиваетесь. Мы за десять лет у себя в компании разработали несколько доморощенных инструментов, которые решают проблемы не специфичные нам, которые не решают специфичные профилировщики. Начиная с того, что у нас в процессе работы появился развесистый, но тем не менее простой инструмент который анализирует thread dump, и выдает по ним статистику, это тем не менее простая утилитка, которую нельзя назвать инструментом. Классик на несколько страничек, который собирает статистику и выдает ее в красивом виде. Дико полезен, потому что нам не надо в production систему подключать какие-то профилировщики, просто thread dump, и все…

И кончая тем, что у нас есть свой собственный инструмент профилирования памяти, который опять же, маленький, его сложно назвать инструментом, который отслеживает, где и что выделяется, причем делает он это, практически не замедляя программы. Причем как коммерческие, так и открытые профилировщики, они тоже умеют отслеживать выделение памяти, но они пытаются решить проблему более сложную, и универсальную. Они пытаются узнать, в каком месте выделение памяти происходит, с полным stack-trace'ом. Это долго, это сильно замедляет. Не используют то же семплирование. Не всегда собирают, тем самым получая не всю статистику, и так далее.

Идут на какие-то свои компромиссы, которые в вашей предметной области, они вам не нужны, у вас есть какие-то свои задачи, которых вы хотите решить при анализе производительности ваших систем.

Теперь я будут отвечать на вопросы (ответы на вопросы с 30:06).

Также можно посмотреть другие статьи-стенограммы с application developer days, скачать все видео с прошедших конференций торрентами ([2011], [2010]) или всю папку по http.
Disclaimer еще раз: Я не автор доклада. Я председатель ПК этой конференции, видео-монтажер, и составитель стенограммы. У автора нет хабрааккаунта, возможно со временем мне удастся его пригласить, например, для комментариев к вашим вопросам.Автор доклада elizarov призван в комментарии и отвечает на вопросы.См. также обсуждение в журнале автора.

BTW, elizarov выступает на конференции ADD-2012 с докладом «Пишем самый быстрый хэш для кэширования данных».
Стас Фомин @belonesox
карма
122,2
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (31)

  • +3
    1. странно, что не упомянута библиотека perf4j
    2. Мне кажется, что чаще без профайлера надо работать, когда приходишь к клиенту у которого инсталлирована твоя программа. Там изменения кода не помогут, а вот знания флагов JVM очень полезны
    3. При разработке мне кажется всё же почти всегда удобнее пользоваться готовыми профайлерами, которые точно также будут делать sampling и инструментацию байт кода, только лучше
    • 0
      Из готовых библиотек для профилировки, которые спасли много часов мне и коллегам, я бы упомянул о: jetm и JavaMelody
    • 0
      Доклад был не о библиотеках профилирования, а о том как сделать самому. Конечно, если готовый профайлер решает вашу задачу, то логично им воспользоваться. А если нет, то приходится что-то делать самому.

  • 0
    я не спец по профелированию, a потому хочу спросить: чем вас не устаривают jconsole и visualvm?
    • 0
      Если «вас» — это вы ко мне, то разочарую. Я мало программирую на Java, я лишь составил стенограмму выступления автора, у которого нет habraaccounta (это самый первый абзац текста).

      Но напомню посыл автора — в профилировании живого высоконагруженного приложения, только самодельным низкоуровневым профилированием вы сможете нащупать компромисс между потерями производительности и получением нужной информации.
      • +2
        Смысл доклада передан верно :)
      • 0
        … но аккаунт у меня есть.
        • 0
          Тчорт! Я искал! Сейчас внесу правки в текст.
  • +1
    Порадовало, что упомянули ASM: приходилось по одной учебной дисциплине заниматься динамическим программным анализом с этой библиотекой, было интересно.
  • +2
    … вы убъете кучу времени в нем разбираясь. Инструменты надо учиться, а делать что-то самому, конечно, намного приятней…
    — следуя вашей логике так и Java неинтересно, лучше свой язык написать.
    А вообще вы или неоговораиваете почему не решили использовать существующие наработки или у вас в отделе разработки бардак и все делают что ему вздумается. Слишком сложные сторонние инструменты, докажите, покажите что вы провели анализ предметной области, а не занимаетесь разработкой велосипедов с квадратными колёсами.
    То что вы сказали о профайлинге аллокаций памяти, я даже как человек который не занимается активной разработкой под Java, а просто пасивно просматривая статьи по теме, скажу что это полное незнание предмета.
    Рассмотрим например продукт foursquare engineering.foursquare.com/2012/02/02/heapaudit-jvm-memory-profiler-for-the-real-world/ — с вашим подходом что вы вообще увидите при доступе к памяти другого ядра в NUMA-архитектуре?
    Вы вообще не упомянули DTrace, на какой вообще Java вы пишете? JDK 1.1.3?
    Возможно для разработки собственных инструментов действительно были причины, но только что я постоянно отмечаю у наших докладчиков, что они сошлются на «неведомые» причины (мы крутая финансовая организация) и считаю что «прокатит».
    Вы упоминаете «финансы», «большую производительность», сравните например эту статью www.infoq.com/articles/scalable-java-components и ваш доклад, ваш получился из разряда «я выучил Java».
    Как я бы улучшил ваш доклад:
    — выбросить финансы и высокие нагрузки, поскольку это формирует завышеные ожидания к уровню доклада
    — Назвать обзорный доклад native-средств Java, для случаев когда нужно разобраться в существующей системе которую нельзя трогать или к которой нет доступа, например post-mortem dump от клиента, или система у клиента к которым ограничен прямой доступ.
    — Говорить о профилировании и не сказать ни слова о DTrace, скажет скорее о незнании предмета.
    — Если упоминаете о существовании стороних инструментов, хороший докладчик приведёт хорошие аргументы для сравниения, а не отделается общими фразами.
    • +1
      Бывает масса случаев когда и язык свой надо написать. Всё зависит от стоящей перед вами задачи. В любом случая, я считаю что каждый обязан знать как именно устроен компилятор, виртуальная машина, и операционная система и в принципе понимать как это можно всё написать. Тогда будет программист будет ценить уже готовые инструменты и понимать в каких случаях их нужно применять.

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

      • 0
        Суть моей претензии в том что вы своими «финансовая сфера, высокие нагрузки» сформировали свосем другой уровень ожиданий к докладу, тогда как в нём показаны азы.
        • 0
          К сожалению, очень часто говоря про «финансовую сферу и высокие нагрузки» начинают с придыханием рассказывать про какие-нибудь много-мегабайтные каркасы, библиотеки и иснтрументы, при этом вообще не понимая азов: как они устроены изнутри, почему и на каких принципах они работают. Инстументы это отличная вещь, если вы понимаете как они устроены. Например, зная внутренее устройство DTrace намного проще понимать когда его нужно применять, а когда нет.

          Мне казалось, что в анонсе своего доклада на ADD-2011 я достачно полно расклыл суть того, о чем я буду говорить (цитирую): «В докладе пойдет речь о методиках изучения производительности Java приложений без использования готовых сторонних инструментов профилирования, а используя не так широко известные встроенные в JVM возможности (threaddumps, java agents, bytecode manipulation).»

          Как это моголо сформировать какие-то ложные ожидания?
          • 0
            Похоже я слишком развращён западными докладчиками и когда слышу финансы и высокие нагрузки я ожидал услышать что-то вроде:
            martinfowler.com/articles/lmax.html
            www.infoq.com/presentations/Tuning-Java-for-Virtual-with-EM4J
            www.infoq.com/presentations/Java-without-the-GC-Pauses
            www.infoq.com/presentations/Understanding-Java-Garbage-Collection
            Куча графиков, объяснений.
            Думаю мы можем прекратить дискусию. Вы сделали интересный доклад, я ожидал услышать что-то для себя новое, но судя по откликам, вы свою аудиторию нашли.
            • 0
              Спасибо. Моя цель была сделать доклад интересным для программистов, которым не приходилось глубоко разбираться в возможностях JVM. Безусловно, если Вы знакомы c threadumps, java agents и bytecode manipulation, то Вы вряд ли найдете в этом докладе что-то нового для себя. Так же как, например, люди профессионально многие годы занимающиеся дизайном и оптимизацией высоконгруженных финансовых приложений не найдут для себя много новой информации в перечисленных выше докладах очень уважаемых людей. И это естественно, ибо это не научные статьи с новыми никому доселе не известными знаниями. Обо всём этом можно было прочитать много лет назад. У каждого доклада своя аудитория.

              Еще раз спасибо за проявленный интерес и коментарии. Буду пытаться в будущих докладах расширить «диаппазон», то есть буду стараться сделать их интересными более широкому кругу слушателей.
    • +1
      Кстати, большое спасибо за ссылку на Foursquare Heapaudit!

      Приятно видеть, когда в других компаниях (в данном случае Foursquare) думают точно также как и мы — если готовые инструменты не решают нашу задачу (и да, мы, как я уверен и Foursquare, очень глубоко знакомы с DTrace и он эту задачу не решает), то инстурумент для её решения надо писать самим. Мы сделали это в 2005 год (и в прошлом году у меня долшли руки сделать на эту тему доклад).

      Тот факт, что Foursquare сделало это сейчас, говорит о том, что DIY Java Profiling не потеряло актуальность за прошедшие 7 лет. Кстати, мы (Devexperts) тоже планируем выложить наш инструмент под открытой лицензией. Stay Tuned!
  • НЛО прилетело и опубликовало эту надпись здесь
  • +2
    Единственный реальный выход — это использовать нативный код, для x86 процессора есть такая замечательная инструкция rdtsc, которая возращает счетчик количества тактов процессора. Напрямую к ней доступа нет, можно написать на C однострочный метод, который вызывает «rdtsc», а дальше слинковать его с Java-кодом, и вызывать из Java. Этот вызов вам займет сто тактов

    Судя по этой статье, System.nanoTime() как раз и будет порядка 100 тактов на TSC таймере.

    Жаль, автора тут нет, хотелось бы услышать комментарий…
    • +1
      Тем более что книга Effective Java рекомендует использовать nanoTime для измерения интервалов…
    • +2
      Залез в исходники OpenJDK, nanoTime для Linux использует clock_gettime(CLOCK_MONOTONIC) для получения времени, который, в свою очередь, использует либо TSC, либо HPET на x86 для получения точного времени (ядро 3.3.5)

      Т.е. утверждение «Напрямую к ней доступа нет» — неверно, System.nanoTime() использует инструкцию rdtsc, по крайней мере в Linux 3.3.5 (а реально, и в гораздо более ранних версиях тоже).

      Получается, что и nanoTime, и currentTimeMillis используют native-вызовы, но nanoTime должна быть быстрее чем currentTimeMillis, т.к. использует инструкцию rdstc. Чуть позже попробую потестировать.
      • 0
        Чуть позже попробую потестировать.

        Было бы интересное дополнение («нанотехнологии на марше»). Ждем.

      • 0
        Набросал очень простой микробенчмарк на Google Caliper
        Вот исходники, каждый может проверить на своей машине github.com/relgames/ClockTest

        Мои результаты:
        Intel® Pentium® D CPU 3.20GHz
        Linux 2.6.38-15-generic #59-Ubuntu SMP Fri Apr 27 16:04:29 UTC 2012 i686 i686 i386 GNU/Linux
        Java(TM) SE Runtime Environment (build 1.7.0_04-b20) Java HotSpot(TM) Client VM (build 23.0-b21, mixed mode)

        relgames@oleg:~/myprojects/ClockTest$ java -jar target/clocktest-1.0-SNAPSHOT-jar-with-dependencies.jar --trials 5
         0% Scenario{vm=java, trial=0, benchmark=CurrentTimeMillis} 0,66 ns; σ=0,02 ns @ 10 trials
        10% Scenario{vm=java, trial=1, benchmark=CurrentTimeMillis} 0,67 ns; σ=0,01 ns @ 10 trials
        20% Scenario{vm=java, trial=2, benchmark=CurrentTimeMillis} 0,66 ns; σ=0,00 ns @ 3 trials
        30% Scenario{vm=java, trial=3, benchmark=CurrentTimeMillis} 0,67 ns; σ=0,01 ns @ 10 trials
        40% Scenario{vm=java, trial=4, benchmark=CurrentTimeMillis} 0,67 ns; σ=0,01 ns @ 10 trials
        50% Scenario{vm=java, trial=0, benchmark=NanoTime} 0,66 ns; σ=0,01 ns @ 10 trials
        60% Scenario{vm=java, trial=1, benchmark=NanoTime} 0,67 ns; σ=0,01 ns @ 10 trials
        70% Scenario{vm=java, trial=2, benchmark=NanoTime} 0,67 ns; σ=0,01 ns @ 10 trials
        80% Scenario{vm=java, trial=3, benchmark=NanoTime} 0,68 ns; σ=0,01 ns @ 10 trials
        90% Scenario{vm=java, trial=4, benchmark=NanoTime} 0,68 ns; σ=0,01 ns @ 10 trials
        
                benchmark trial    ns linear runtime
        CurrentTimeMillis     0 0,664 =============================
        CurrentTimeMillis     1 0,669 =============================
        CurrentTimeMillis     2 0,656 ============================
        CurrentTimeMillis     3 0,665 =============================
        CurrentTimeMillis     4 0,667 =============================
                 NanoTime     0 0,665 =============================
                 NanoTime     1 0,674 =============================
                 NanoTime     2 0,674 =============================
                 NanoTime     3 0,680 ==============================
                 NanoTime     4 0,676 =============================
        


        3.20GHz — это 0.3125нс на такт, 2 такта это 0.625нс
        Т.е. каждый вызов — это 2 такта, погрешность вызвана неточностью расчетов в ядре (тут принимаю комментарии, ибо не вполне уверен, почему так)

        Выводы: на моей машине вызов currentTimeMillis и nanoTime занимает одинаковое время, а именно 2 такта.
        • +2
          Вы ничего не замеряете в своем коде. HotSpot (-server уж точно) оптимизует (уберет) ваши вызовы, ибо вы ни как не используете их результат. Я рекомендую внимательно изучить документацию к Google Caliper (там вполне адекватная подборка советов), а также другую литературу посвященную написанию microbenchmarks.
          • 0
            Они сами так делают.

            Переписал. Вот новые результаты:
            0% Scenario{vm=java, trial=0, benchmark=CurrentTimeMillis} 500,36 ns; σ=0,42 ns @ 3 trials
            10% Scenario{vm=java, trial=1, benchmark=CurrentTimeMillis} 509,32 ns; σ=4,70 ns @ 5 trials
            20% Scenario{vm=java, trial=2, benchmark=CurrentTimeMillis} 500,87 ns; σ=0,76 ns @ 3 trials
            30% Scenario{vm=java, trial=3, benchmark=CurrentTimeMillis} 496,79 ns; σ=2,31 ns @ 3 trials
            40% Scenario{vm=java, trial=4, benchmark=CurrentTimeMillis} 501,60 ns; σ=3,01 ns @ 3 trials
            50% Scenario{vm=java, trial=0, benchmark=NanoTime} 505,25 ns; σ=4,90 ns @ 3 trials
            60% Scenario{vm=java, trial=1, benchmark=NanoTime} 513,66 ns; σ=4,92 ns @ 3 trials
            70% Scenario{vm=java, trial=2, benchmark=NanoTime} 518,22 ns; σ=6,33 ns @ 10 trials
            80% Scenario{vm=java, trial=3, benchmark=NanoTime} 507,69 ns; σ=5,77 ns @ 10 trials
            90% Scenario{vm=java, trial=4, benchmark=NanoTime} 510,61 ns; σ=2,24 ns @ 3 trials
            
                    benchmark trial  ns linear runtime
            CurrentTimeMillis     0 500 ============================
            CurrentTimeMillis     1 509 =============================
            CurrentTimeMillis     2 501 ============================
            CurrentTimeMillis     3 497 ============================
            CurrentTimeMillis     4 502 =============================
                     NanoTime     0 505 =============================
                     NanoTime     1 514 =============================
                     NanoTime     2 518 ==============================
                     NanoTime     3 508 =============================
                     NanoTime     4 511 =============================
            


            Похоже, Вы правы. ~1600 тактов на вызов.

            А как JVM может выкидывать вызов native метода? Или она знает, что эти методы ничего не меняют, а только читают счетчик?

            Чуть позже сделаю версию с native rdtsc; похоже, в Linux тратится много тактов на ядро. Как я понял из исходников, они в начале опрашивают wall clock, и потом прибавляют результат rdtsc, чтобы получить время в нс.
            • 0
              В НоtSpot эти методы intrinsic.
    • +1
      System.nanoTime делает много больше, чем RDTSC. В числе прочего он пересчитывает такты процессора в наносекунды и гарантирует неубываемость результатов. Что именно вам подходит лучше (RDTSC или System.nanoTime) зависит от вашей конкретной ситуации.

      Например, в своей серии статей о производительности я использую System.nanoTime для всех замеров, но в своей практике сталкивался с реальными случаями, когда был нужен именно RDTSC.
      • 0
        Поискал по Вашему блогу, не нашел упоминаний про RDTSC, жаль :)
        • 0
          Блог это не мой memory dump :) Логичного повода упомянуть про RDTSC в блоге пока не было. Да и вообще вряд ли я смогу написать что-то новое про RDTSC — Google вам поможет если вы хотите про него больше знать.
  • +1
    Есть у меня аккунт на хабре, только я сюда не пишу. Я пишу все свои техничесные заметки в свой журнал. Там же есть запись про этот доклад, в которой содержаться мои дополнительные мысли и разъяснения на тему доклада. Но я готов отвечать на вопросы и здесь.
  • 0
    Весьма познавательно! Вольно перевел вашу статью-стенограмму на английский для коллег :) Результат тут.
  • 0
    Инструменты надо учиться, а делать что-то самому, конечно, намного приятней.
    Обычно это основной аргумент у разработчиков для разработки нового велосипеда)

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.