Чем я мерю
Короткий список того, что реально открывается каждую неделю, и правила, в каком порядке я это делаю. Ничего экзотического: всё перечисленное либо входит в систему, либо ставится одной командой.
Стенд
Все числа в записях получены на одном из двух узлов, и я каждый раз указываю, на каком.
| узел | состав |
|---|---|
| x86-64 | 8 ядер, 32 ГиБ, Linux 6.6, glibc 2.39, gcc 13.2 |
| aarch64 | 4 ядра, 8 ГиБ, Linux 6.6, glibc 2.39, gcc 13.2 |
Перед замером фиксирую частоту: регулятор в performance, турбо выключено. Без этого разброс между прогонами доходит до 15 %, и любая правка на пять процентов становится неотличима от шума. Пять прогонов, беру медиану, первый прогон выбрасываю как прогрев.
Порядок, а не набор
- Счётчики.
perf stat: такты, инструкции, IPC, промахи кеша, переключения контекста. Это три-четыре числа, которые говорят, чего программа ждёт - вычислений, памяти или ядра. - Профиль.
perf record -e cycles:pp -g, и только после того, как счётчики показали, что узкое место вообще в пользовательском коде. - Состав обращений к ядру.
strace -f -cодин раз, чтобы увидеть, какие вызовы есть. Дальше считаю их точками трассировки, потому чтоptraceзамедляет объект на порядок. - Память.
mallinfo2()изнутри и/proc/<pid>/smaps_rollupснаружи.
Нарушение этого порядка стоило мне трёх дней в разборе про memcpy: профиль был снят первым и ответил не на тот вопрос.
Сборка
# рабочая сборка, в которой я и мерю
-O2 -g -fno-omit-frame-pointer
# отдельная сборка для проверок, гоняется до слияния ветки
-O1 -g -fsanitize=address,undefined,alignment
# компоновка с типами, ловит несовпадение прототипов между файлами
-flto -Wlto-type-mismatch
-fno-omit-frame-pointer в сборке для замеров обязателен: без него стек в профиле собирается эвристиками и врёт на вложенных вызовах. Потеря скорости от этого на моих задачах в пределах процента, то есть меньше разброса между прогонами.
Отладка падений
Корки включены на всех стендах: core_pattern пишет файл с номером процесса и временем, лимит снят. В gdb первыми тремя командами всегда идут bt, x/i $pc и печать регистра, который участвует в упавшей инструкции - этого хватило бы в истории с SIGBUS, если бы я посмотрел туда сразу.
Дальше по надобности: objdump -d для одной функции, readelf -S когда вопрос про секции и выравнивание, nm когда символ не тот, addr2line для адреса из журнала.
Чего у меня нет в наборе
- Микробенчмарков в отрыве от данных. Функция, которую греют одним и тем же буфером в цикле, живёт в кеше и показывает скорость, которой в проде не будет никогда.
- Графических профилировщиков. Не из принципа - просто текстовый вывод копируется в запись и в письмо, а картинка нет.
- Оптимизаций без счётчика до и после. Если правка не меняет ни одного измеримого числа, она не оптимизация, а правка вкуса.
Одна мелочь про perf
На большинстве машин perf_event_paranoid выставлен так, что счётчики недоступны непривилегированному процессу. Я поднимаю права на время замера и возвращаю значение обратно сразу после - оставлять открытым не стоит, а забыть вернуть очень легко, поэтому у меня это одна строка в скрипте замера, которая делает и то и другое.
Флаги, которыми собираю
| Зачем | Флаги |
|---|---|
| замер скорости | -O2 -g -fno-omit-frame-pointer |
| поиск UB и переполнений | -O1 -g -fsanitize=address,undefined,alignment |
| гонки данных | -O1 -g -fsanitize=thread |
| перед слиянием ветки | -flto -Wlto-type-mismatch |
-fno-omit-frame-pointer здесь не прихоть: без него perf -g на -O2 строит стек по эвристике и врёт, а эти несколько процентов скорости не стоят испорченного профиля. Санитайзеры между собой не дружат: ASan и TSan вместе не собираются, поэтому прогоны раздельные.
Какие счётчики за что отвечают
Порядок замера жёсткий - сначала счётчики, потом профиль, - но и среди счётчиков есть разделение труда. Смешивать их выводы в одно «медленно» - главная ошибка, которую я за собой ловлю чаще всего, и половина записей на сайте ровно про это.
| Счётчик | Что ловит | Запись |
|---|---|---|
| LLC-load-misses | промахи кеша данных | профиль врёт |
| dTLB-load-misses | обход таблицы страниц | промахи TLB |
| futex в strace -c | ожидание на блокировке | фьютекс в профиле |
| perf c2c, HITM | ложное разделение линий | ложное разделение |
| insn per cycle | простаивает ли конвейер | 0,32 IPC |
Короткий словарь
- HITM
- hit-modified: ядро читает кеш-линию, которую другое ядро только что изменило - метка ложного разделения или честной конкуренции за данные.
- walk_active
- такты, в которые MMU обходит таблицу страниц после промаха TLB; видно отдельным счётчиком, в профиле по символам размазано.
- off-CPU
- время, когда поток спит, а не считает; профиль по циклам его не видит, и хвосты задержки живут именно здесь.
- RSS
- резидентная память процесса; после
freeне обязана падать - страницы остаются за процессом доmalloc_trimили давления памяти.