Поддержка профилирования кода программы на низком уровне
Тема этой заметки навеяна жалобами программистов на неудобные или недостаточно полные средства профилирования кода программы,
используемые при поиске «узких мест» с точки зрения производительности. Например, вот:
http://rsdn.org/forum/cpp.applied/7731507.flat
На мой взгляд, одной из причин этого является почти полное отсутствие поддержки такого профилирования на низком уровне, т.е. со стороны процессора.
Помнится, в начале 90-х у меня был настольный компьютер (к сожалению, забыл марку, по-моему, немецкий), у которого на передней панели был спидометр!
Я не шучу. На его передней панели был индикатор из светодиодных сегментов для трех цифр. Согласно очень мутному описанию, он показывал число команд
в штуках, то ли в среднем за такт, то ли за какой-то фиксированное время. В общем, он показывал какое-то число, обычно близкое к 1.00 и чем оно было выше,
тем более производительным был в этот момент процессор. Прекрасная вещь для поиска «узких мест»! И, главное, специально для замеров в программе
делать ничего не надо было: поменял очередной раз код, запустил тестовый прогон и смотришь на индикатор. Тактовые частоты тогда были невысокие,
считалось все относительно медленно, вполне можно было разглядеть любые незначительные отклонения производительности.
С его помощью я, отчасти из спортивного интереса, отчасти из уязвленного самолюбия, пытался ускорить работу транслятора с ассемблера RASM-86.
Он выполнял тест за 23.4 секунды, а борландовский MASM выполнял этот тест за 8.3 секунды. С помощью этого волшебного индикатора я быстро нашел
все узкие места и неудачные команды (конечно, они оказались в лексическом анализаторе) и добился выполнения теста за 5.6 секунды.
Вроде бы сейчас, имея в Windows диспетчер задач с его графиками производительности, не нужны никакие другие индикаторы.
Однако в современной многоядерной и многопоточной системе трудно бывает понять, что на что влияет. Хотелось бы более простых и понятных средств.
По-моему, аппаратно это несложно добавить в любой современный процессор. Поскольку я хорошо знаю лишь архитектуру x86, попробуем помечтать на её примере.
В этой системе команд есть операция RDTSC, которая возвращает число тактов с момента включения процессора и к ней можно обращаться в любой момент.
Всякие тонкости, вроде того, что она дает такты с учетом простоев для текущего ядра и при переключении ядер можно получить меньше тактов,
чем при предыдущем запросе, не будем учитывать.
Вся доделка для этой команды, ну, или для новой гипотетической команды RDTSC1 заключается в том, что для неё можно задавать параметры:
начальный и конечный адрес и способ их учета. Кстати, параметры можно задавать через порты самого процессора и тогда его систему команд можно и вообще не менять.
Смысл в том, что при задании начального и конечного адресов внутренний счетчик тактов обнуляется и работает только, когда указатель
RIP/EIP попадает внутрь заданного диапазона адресов. В другом режиме — счетчик начинает работу, когда RIP/EIP строго совпадает с
начальным адресом и заканчивает, когда строго совпадает с конечным адресом.
Такая незначительная доработка процессора позволит использовать его для профилирования без какого либо изменения кода анализируемой программы.
В первом режиме я задаю начало и конец своей, может быть небольшой подпрограммы внутри огромного проекта, причем начало —
это необязательно точка входа в подпрограмму. Затем просто запускаю главную программу, возможно, заканчиваю или останавливаю её прогон,
а затем читаю получившийся счетчик — число тактов, которое управление находилось именно в этой подпрограмме, без учета, например,
времени работы системных вызовов (так как они наверняка окажутся вне заданного диапазона адресов). Во втором режиме я задаю точку входа
в подпрограмму и точку выхода из неё. И получаю счетчик тактов нахождения в подпрограмме уже с учетом всех вложенных вызовов.
Только в том случае, если точек выхода несколько, возможно, придется слегка менять код программы, чтобы такое профилирование
давало верные результаты.
В большинстве же случаев можно не ставить никаких специальных отладочных вызовов, не использовать никаких прерываний и
вообще никак не влиять на работу кода. И в результате получать некоторую количественную характеристику производительности
выполнения анализируемого участка кода. И при попытках улучшения сравнивать не все время работы огромной программы,
а лишь с такой же предыдущей количественной характеристикой, т.е. становится возможным увидеть изменения
производительности в масштабах микро- и наносекунд, не запуская миллионы циклов теста.
Таких команд, правда, пока нет. Но, возможно, что-то похожее именно для поддержки профилирования в будущем и появится.
Автор: Д.Ю.Караваев. 06.06.2020
Опубликовано: 2020.06.06, последняя правка: 2020.06.08 18:10
Отзывы
✅ 2020/06/08 18:19, Автор сайта #0
Когда-то читал легенду, что программисты Silicon Graphics были теми, кто «заказывали» себе систему команд и архитектуру процессора. Что для них был первичен софт, а вот железо они под себя подбирали. Но, правда, подтверждения этих легенд я нигде не встречал. Но, наверное, счастливые были люди — программисты Silicon Graphics :)
Шансов на то, что Ваши пожелания Intel учтёт, даже не ничтожны. Их просто нет. Все коды уже заняты какими-то командами. Незанятых кодов так мало, что их «растягивают», задают новым командам очень большую длину, чтобы оставить ещё какой-то резерв. От этого коды новых команд очень длинны, что не есть хорошо.
Жаль, что для программистов самый низкий доступный уровень — это уровень команд, в то время, как ещё существует уровень микрокоманд. Вот была бы возможность написать пользовательскую микропрограмму и вызвать её по коду «пользовательская команда». Тогда «хотелки» было бы проще реализовать :)борландовский MASM Вроде бы MASM — это Microsoft, а у Borland был TASM? Или я заблуждаюсь?✅ 2020/06/08 21:53, kt #1
Шансов на то, что Ваши пожелания Intel учтёт, даже не ничтожны. Их просто нет. Да, я тоже не рассчитываю, что разработчики Intel прислушаются к моим советам. Но вероятность, что они сами введут что-нибудь подобное, отлична от нуля. Там же тоже полно программистов и у них тоже стоят проблемы оптимизации.Все коды уже заняты какими-то командами. Незанятых кодов так мало, что их «растягивают», задают новым командам очень большую длину Это мелочь. Ведь к гипотетическим командам профилирования не нужно будет часто обращаться, в отличие от системных вызовов, которые расставляют профилировщики. Да и не все коды заняты. Вспомните несчастную INTO. Кроме этого, возможно чтение-запись через порты самого процессора, тогда никаких новых команд не требуется, достаточно уже имеющихся RDMSR/WRMSR или RDPMC на все случаи жизни.Жаль, что для программистов самый низкий доступный уровень — это уровень команд, в то время, как ещё существует уровень микрокоманд. Возможность менять микрокод физически, вероятно, есть. Исправляли же ошибки перепрограммированием процессоров в каждом компьютере. Правда, информацией о таких работах никто делиться не спешит.
К слову, опубликовано «Руководство по эффективному программированию на платформе .Эльбрус», наконец, с описанием настоящих команд этого процессора. На мой взгляд, это надо было делать лет 30 назад, но тогда тоже никто не спешил с публикованием.
TurboASM и MakroASM, да, перепутал. Добавить свой отзыв
Написать автору можно на электронную почту mail(аt)compiler.su
|