Чего не хватает для идеального профилирования кода

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!

Знаете ли вы, как работает такая серьезная программа оценки производительности, как Intel VTune Amplifier? Не в смысле интерфейса с пользователем и разных возможностей, а на какой аппаратной поддержке она основана?

Я попытался найти об этом информацию, но как-то разработчики этой программы не делятся с пользователями объяснениями, как именно они получают данные о пользовательской программе. Вероятно, никаких секретных команд и способов там нет. Все основано на сигналах прерываний и установке аппаратных и программных контрольных точек (которые тоже вызывают прерывания). Ну и, конечно, на чтении «телеметрии» самого процессора.

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

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

Т.е. достаточно поставить обработчик на системный таймер и извлекать из автоматически запоминаемого контекста указатель текущей команды RIP (EIP). Затем, имея адреса подпрограмм, легко определить, какая именно подпрограмма работала в момент прерывания, и статистически учитывать это в профиле выполняемого кода. Поиск подпрограммы по адресу RIP процесс, конечно, тоже не мгновенный и не бесплатный, но его можно отложить, а в реальном времени лишь запоминать в памяти очередное значение RIP.

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

Помнится, давным-давно, в начале 90-х, у меня был настольный компьютер (к сожалению, забыл марку, по-моему, немецкий), у которого на передней панели был спидометр! Я не шучу. На его передней панели был индикатор из светодиодных сегментов для трех цифр. Согласно очень мутному описанию, он показывал число команд в штуках, то ли в среднем за тик, то ли еще за какое-то фиксированное время. В общем, он показывал какое-то число, обычно близкое к 1.00, и чем оно было выше, тем более производительным был в этот момент процессор.

По тем временам прекрасная вещь для поиска «узких мест»! И, главное, специально для замеров в программе делать ничего не надо было: поменял очередной раз код, запустил тестовый прогон и смотришь на индикатор. Ну да, в те стародавние времена и тактовые частоты были невысокие, считалось все относительно медленно, и объем ПО был небольшим, да и MS DOS с современной точки зрения, это, вероятно, случай отсутствия ОС. Вполне можно было разглядеть на индикаторе любые незначительные отклонения производительности.

С его помощью я, отчасти из спортивного интереса, отчасти из уязвленного самолюбия, пытался ускорить работу транслятора с ассемблера RASM-86. Он выполнял тест за 23.4 секунды, а микрософтовский MASM выполнял этот же тест за 8.3 секунды. С помощью этого волшебного индикатора я быстро нашел все узкие места и неудачные команды (конечно, они оказались в лексическом анализаторе) и добился выполнения теста за 5.6 секунды.

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

По-моему, простую аппаратную поддержку профилирования кода несложно добавить в любой современный процессор. Если взять процессоры Intel, то такая поддержка могла бы быть реализована на внутреннем счетчике, подобному счетчику системных тактов, который можно прочитать с помощью инструкции RDTSC. Но в отличие от счетчика, читаемого RDTSC, дополнительный внутренний счетчик должен увеличиваться с каждым тактом не с момента включения процессора, а только при выполнении условий, заданных в программе.

Разумеется, никаких новых команд вводить для этого не требуется. Задание условий для гипотетического дополнительного счетчика можно делать и через запись в порт самого процессора командой WRMSR, а чтение значения счетчика – командой чтения из порта процессора RDMSR. Кроме этого, запись в порт должна вызывать и сброс счетчика, чтобы не создавать для этого отдельное действие.

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

Смысл в том, что при задании начального и конечного адресов внутренний счетчик тактов обнуляется и теперь увеличивается на каждом такте только, когда указатель RIP/EIP попадает внутрь заданного диапазона адресов. В другом режиме — счетчик начинает работу, когда RIP/EIP строго совпадает с начальным адресом и заканчивает, когда строго совпадает с конечным адресом.

Такая незначительная доработка процессора позволит использовать его для профилирования выполняемого кода без какого либо изменения самого кода анализируемой программы и совершенно прозрачно для нее.

В первом режиме я задаю начало и конец своей, может быть небольшой подпрограммы внутри огромного проекта, причем начало — это необязательно именно точка входа в подпрограмму. Затем просто запускаю главную программу, возможно, заканчиваю или останавливаю её прогон, а затем читаю получившийся счетчик — число тактов, которое управление находилось именно в этой подпрограмме, без учета, например, времени работы системных вызовов (так как они наверняка окажутся вне заданного диапазона адресов).

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

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

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

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

Источник: https://habr.com/ru/post/540792/


Интересные статьи

Интересные статьи

Код, который большинство из нас пишет, как правило содержит ещё текст – разные элементы интерфейса, описания, содержание имейлов и так далее. Но мы, программисты, не все ...
А вы знали что метрики покрытия вашего кода врут? В 2003 году Дерик Ретанс (Derick Rethans) выпустил Xdebug 1.2. Впервые в экосистеме PHP появилась возможность собирать данные о п...
Компании переполнили рынок товаров и услуг предложениями. Разнообразие наблюдается не только в офлайне, но и в интернете. Достаточно вбить в поисковик любой запрос, чтобы получить подтверждение насыще...
Приступая к животрепещущей теме резервного копирования на «Битрикс», прежде всего хотелось бы поблагодарить разработчиков, реализовавших автоматическое резервное копирование в облачное хранилище в вер...
В первой части рассказа по мотивам выступления Дмитрия Стогова из Zend Technologies на HighLoad++ мы разбирались во внутреннем устройстве PHP. Детально и из первых уст узнали, какие изменениях в ...