Ориентация главы
Первый шаг оптимизации — не правка кода, а умение измерять — эта глава есть методология работы с производительностью (глава 137 — практика). Измерительная инфраструктура XRT даёт образец ответов: профилированные бенчмарки (config/performance_profiles.json: каждый профиль — исходник бенчмарка + аргументы + политика выборки — «что гонять» есть декларация, не импровизация); базовая линия среды (четырнадцать элементов: платформа/CPU/аффинность/политика питания/компилятор/уровень оптимизации/политика планирования — числа сравнимы только внутри одной линии); статистическая дисциплина (выборки repeats + MAD (медианное абсолютное отклонение) и центральный диапазон — двойной порог разброса; шумные измерения отвергаются, а не усредняются); пороги регрессии (направленные higher 10%/lower 20% — строже к замедлению, бдительнее к ускорению); свип-эксперименты (sweep — систематический обход пространства параметров, не одноточечные проверки). Плюс дисциплина вопросов: бенчмарк отвечает на заданный вами вопрос — вопрос неверен, и самые точные числа бесполезны.
136.1引入
Три провальных начала работы с производительностью: измерение без базы («теперь вроде медленнее» — насколько? С чем сравнить? На какой машине?); сравнение между средами (цифры разработческой и CI-машины смешиваются — поколение CPU/политика питания/фоновая нагрузка всё разное); шум принят за сигнал (однократная выборка с колебанием 3% обработана как регрессия — охота впустую). Общий корень: измерение как «прогнал и посмотрел число» вместо дизайна эксперимента. Измерительная цепочка XRT (measure_performance/measure_size + профили + схема отчёта) фиксирует дизайн эксперимента: профиль объявляет что и как гонять, базовая линия записывает где, порог решает считается ли — человек отвечает только за правильный вопрос.
Дисциплина вопросов в развёртке: перед бенчмарком напишите одну фразу — «я подозреваю, что X в сценарии Y медленнее/быстрее Z»? X — операция (скажем, одиночное включение SPSC), Y — сценарий (ёмкость 4096, два потока), Z — контрольный объект (предыдущая версия/другая структура данных). Не пишется эта фраза — время измерять ещё не пришло. «Сначала с умолчаниями, подключи наблюдение, подстрой по данным» главы 62 здесь принимает форму: прогнать профиль по умолчанию за базу → посмотреть разброс (уровень шума) → спроектировать эксперимент → числа отвечают на вопрос.
136.2概念
Профилированные бенчмарки: декларативные эксперименты
Две скорости smoke и полной: smoke_args — быстрая форма CI (секунды проверки «бенчмарк ещё работоспособен»); полные args — формальная выборка (минуты до статистики). Исходники бенчмарков живут в dev/bench/ (подвешены на поле benchmarks манифеста модуля — очередное потребление манифеста главы 130) — бенчмарки тоже актив под управлением манифеста.
Базовая линия среды: четырнадцать элементов
| Группа | Элементы |
|---|---|
| Платформа | platform / machine / platform_release |
| Процессор | cpu / cpu_count / affinity (аффинность)/power_policy (политика питания) |
| Сборка | compiler_family / compiler_version / arch / optimization / cflags / ldflags |
| Политика | policy (например baseline-unpinned) |
Два чаще всего игнорируемых элемента: power_policy (разница ноутбука в энергосбережении и от сети достигает 2–3 раз — отчёт обязан это записывать); affinity (привязка к ядрам меняет поведение кэша и планирования — у бенчмарка задержек queue есть закреплённый вариант специально для сравнения). Правило сравнения: трендовые выводы делаются только внутри одной линии; расхождение между линиями — сперва подозревать среду, потом код (производительная версия семи элементов размера из главы 131).
Статистическая дисциплина: пороги разброса
max_relative_mad: 0.2 (медианное абсолютное отклонение относительно медианы ≤20%) и max_relative_central_range: 0.3 (центральный диапазон ≤30%) — двойной порог: любое превышение = этот раунд измерения слишком шумный — результат «отклонён», а не «усреднён и дальше». Это водораздел с любительским измерением: любитель усредняет шум (среднее очень стабильно и совершенно бессмысленно — каждый раз в другом месте); дисциплина выявляет шум (отклонить → искать причину → источник помех/больше repeats/другая политика). Медиана прежде: отчёт берёт медиану (устойчива к выбросам); серия выборок записана полностью (прозрачность и пересматриваемость).
Пороги регрессии: направленные двойные пороги
regression: {higher: 0.1, lower: 0.2} — замедление на 10% тревожит, ускорение на 20% тоже тревожит. Чему бдительно ускорение: изменилась среда измерения (ложное улучшение) или заплачено в другом месте (этот профиль быстрее, другой медленнее — гонять надо все профили). Направленная асимметрия прагматична: замедление — прямой ущерб (строгий порог), ускорение — косвенный сигнал (широкий порог).
Свип-эксперименты: sweep
Одноточечный бенчмарк отвечает «насколько быстро эта конфигурация»; sweep отвечает «каково пространство конфигураций» — двумерный свип ёмкость/батч очереди (run_queue_bench_sweep): матрица ёмкость 64..65536 × батч 1..128 — точки перегиба (после батча 32 отдача насыщается) и плато (SPSC деградирует на малой ёмкости) видны с одного взгляда. Свип — место рождения доказательств выбора: ответы «как выбрать ёмкость» главы 21 и «насколько велик батч» главы 68 происходят из свипов, а не из воздуха. Отчёты (QUEUE_BATCH_SWEEP и др.) архивируются в dev/bench — результаты экспериментов — активы первого класса.
136.3示例
Первая полная программа: микро бенчмарк глазами памяти
Программа ниже из examples/memory/stats — чтения stats как самое лёгкое наблюдение производительности:
*/ #include <stdio.h> #include <xrt.h> int main(void) { xmemstats tStats; ptr pSmall; ptr pLarge; /* 开关可查询:先确认当前状态,再打开并清零。 */ printf("before=%d\n", xrtMemStatsEnabled() ? 1 : 0); xrtMemStatsEnable(true); printf("after=%d\n", xrtMemStatsEnabled() ? 1 : 0); xrtMemStatsReset(); /* 一次小块(走池)+ 一次大块(直通)。 */ pSmall = xrtMalloc(48); pLarge = xrtMalloc(4096); if ( (pSmall == NULL) || (pLarge == NULL) ) { xrtFree(pSmall); xrtFree(pLarge); return 1; } xrtFree(pSmall); xrtFree(pLarge); /* 读取窗口内计数(字段语义见文件头读数解释)。 */ xrtMemStatsGet(&tStats); printf("malloc_calls=%llu pooled=%llu direct=%llu backing=%llu\n", (unsigned long long)tStats.MallocCalls, (unsigned long long)tStats.PooledAllocCalls, (unsigned long long)tStats.DirectAllocCalls, (unsigned long long)tStats.BackingAllocCalls); /* 用完关闭,回到零开销模式。 */ xrtMemStatsEnable(false); return 0; }
Что только что произошло. (1) Первый вопрос анализа производительности — часто «сколько выделилось» — на него отвечают четыре чтения stats: здесь 48Б попадает в пул (ноль системных вызовов), а 4096Б идёт напрямую — измеренная на живую граница пула (быстрее чтения документации и всегда согласована с текущей сборкой). (2) Это микронаблюдение (уровень счётчиков) — измерение временного измерения идёт через цепочку bench; но многие «проблемы производительности» решаются уже на уровне счётчиков (backing аномально высок = конфигурация пула не совпадает с размерами бизнеса). (3) Методологическое место: stats — наблюдение нулевой стоимости, всегда включённое (панель здоровья главы 135) и первая остановка расследования производительности — сперва счётчики, потом время.
Вторая полная программа: самопроверочный бенчмарк с детерминированным выводом
Вторая программа из examples/core/version_limits — доверительная форма бенчмарка:
*/ #include <stdio.h> #include <xrt.h> int main(void) { xrtresourcelimits Limits; printf("version=%s\n", xrtVersion()); xrtResourceLimitsInit(&Limits); printf("limits: depth=%u entries=%llu input=%llu\n", (unsigned)Limits.iMaxDepth, (unsigned long long)Limits.iMaxEntries, (unsigned long long)Limits.iMaxInputBytes); return 0; }
Что только что произошло. (1) Образец демонстрирует детерминированный вывод — в сравнении со случайностью временных бенчмарков: в инженерии бенчмарков «воспроизводимость» двояка (детерминированный вывод = каждый раз одно значение; статистический вывод = устойчивое распределение). (2) Умолчания ресурсных границ (128/100000/256МиБ) — скрытые параметры профилей производительности: масштаб входа бенчмарков парсеров стоит за этими шлюзами (тестирование сверхбольших входов требует явного расширения — cflags профиля может инъецировать). (3) Настоящие бенчмарки dev/bench (семейства queue/coroutine/task) больше по форме (многопоточная выборка + статистический вывод), но атомы методологии в этом образце все: фиксированный вход, явный вывод, можно сравнивать.
Анатомия бенчмарк-отчёта (QUEUE_BENCH как шаблон)
Структура dev/bench/QUEUE_BENCH_20260728_XRT2.md и есть шаблон: Sources (исходники бенчмарка + скрипты + форма сборки — воспроизводимый вход); Environment (ОС/CPU/компилятор/флаги сборки/политика планирования — база сама о себе); Matrix (параметры матрицы: элементов на производителя/ёмкость/числа производителей-потребителей/батч/число выборок — дизайн эксперимента виден); Results (серия выборок + median по каждому профилю — прозрачные сырые данные); Interpretation (интерпретация — соединение выводов и чисел: путь одиночных элементов в ожидаемом диапазоне, пакетное резервирование эффективно амортизирует, батч — предпочтительный путь высокой пропускной способности). Пять секций, каждая на своём месте: без Sources невоспроизводимо, без Environment несравнимо, без Matrix неоцениваемо, без Results недостоверно, без Interpretation неприменимо. Самопроверка отчёта по этим пяти секциям — это же формат приёмки упражнений главы 136 и вызова главы 137.
136.4契约
- Декларация профиля: исходник бенчмарка + args + две скорости smoke + политика — что гонять, не импровизация.
- Четырнадцатиэлементная база: платформа/CPU/аффинность/питание/сборка/политика — тренды только внутри одной линии.
- Двойной порог разброса: MAD≤20% + центральный диапазон ≤30% — превышение значит отклонение, не усреднение.
- Отчёт по медиане: серия выборок записана полностью; медиана устойчива к выбросам.
- Направленные пороги: медленнее на 10% строго / быстрее на 20% широко — быстрое тоже сигнал.
- Свип-эксперименты: систематический обход пространства параметров — место рождения доказательств выбора; отчёты в dev/bench.
- Две скорости smoke/полная: секундная проверка CI + минутная формальная выборка.
- Бенчмарки есть актив: benchmarks подвешены на манифест — управление в том же жизненном цикле, что у исходников.
- Вопросы прежде: не пишется «подозреваю X против Z при Y» — время измерять не пришло.
Типичные ошибки
Симптом: «после оптимизации быстрее на 8%» — 8% внутри шума однократного прогона в 3% бессмысленно; перезапуск может развернуть направление.
Причина: у однократной выборки нет распределения — без разброса нет разрешения. Выборка repeats + пороги — минимальная конфигурация различения сигнала и шума.
run_bench_once(); /* однократная выборка */ if ( faster_by_8pct() ) { merge(); } /* вывод внутри шума */
/* профилированная выборка: repeats=5 + пороги разброса; медиана против базы */ measure_profile("queue", /*repeats*/ 5, /*gates*/ true); if ( regression_significant(&New, &Base) ) { conclude(); }
Симптом: цифры CI всегда на 20% лучше локальных — непонятно, кому верить.
Причина: базы разные (CPU/питание/аффинность/нагрузка). Сперва выровнять базу (та же машина, та же политика, та же сборка), потом сравнивать; между средами сравнивать только структурные выводы (форму кривой батча), не абсолютные числа.
/* локально 9M, CI 11M items/s */ if ( ci_faster() ) { trust_ci(); } /* базы разные: бессмысленно */
/* A/B на той же машине и политике: среда тождественна, меняется только код */ measure_ab(same_machine(), build_a(), build_b()); /* между машинами сравнивать только форму sweep-кривых (структурные выводы) */
Симптом: оптимизация ускорила бенчмарк на 30%, прод не изменился — горячий путь бенчмарка отличается от реальной нагрузки.
Причина: вопрос бенчмарка был неверен (дисциплина вопросов). Какова доля батча в реальной нагрузке? Каково распределение ёмкостей? Сперва профилировать реальное (stats/счётчики), потом проектировать бенчмарк — вопросы бенчмарка приходят из реальности.
/* меряется только путь одиночных элементов */ bench_single_enqueue(); /* оптимизируем его */ /* нагрузка продакшна на 90% пакетная - оптимизация не ощущается */
/* stats вперёд: поведение продакшна посчитано -> преобладает батч */ /* бенчмарк включает пакетные профили + sweep фиксирует батч - вопрос из реальности */ bench_batch_sweep();
Практические задания
measure_performance.py --profiles task (или соответствующий профиль) — прочитать секции базы и статистики отчёта. Критерии приёмки: можете назвать значения вашей машины среди четырнадцати элементов; проходят ли пороги разброса.
Внести малое изменение в любой исходник бенчмарка (скажем, добавить ветвь) — профилированное A/B (та же машина, та же политика). Критерии приёмки: отчёт корректно заключает «значимой разницы нет»; вы можете объяснить, почему малое изменение невидимо при этих порогах.
По образцу свипа очереди: выбрать параметр (скажем, ёмкость пула) и снять выборку в 5 точках — написать скрипт свипа + табличный отчёт (четыре секции среда/матрица/результаты/интерпретация — структура отчёта QUEUE_BENCH). Критерии приёмки: точка перегиба различима; интерпретация даёт рекомендацию выбора; отчёт архивируется в стиле dev/bench.
Итоги
| Тема | Кратко |
|---|---|
| Ядро метода | дизайн эксперимента — декларация профиля / блокировка базы / статистические пороги / направленные пороги |
| Профили | profiles.json: исходник + args + две скорости smoke + политика |
| База | четырнадцать элементов; питание/аффинность легче всего забыть; тренды только в одной линии |
| Пороги разброса | MAD≤20% + центральный диапазон ≤30% — превышение значит отклонение, не усреднение |
| Отчёт | median + полная запись выборок; схема 2 |
| Регрессия | 10% медленнее строго / 20% быстрее широко — быстрое тоже сигнал |
| sweep | обход пространства параметров — место рождения доказательств выбора; отчёты — активы первого класса |
| Две скорости | smoke секунды в CI / полная минутная выборка |
| Дисциплина вопросов | «подозреваю X против Z при Y» — не пишется, не время измерять |
| Микронаблюдение | счётчики stats вперёд — сперва распределения, потом время |
📖 Справочники API в этой главе:memory_stats (5 записей)core (25 записей)
Полные сигнатуры, ограничения параметров, возвращаемые значения и ошибки