8. Сравнение версий кода
Доказательная оптимизация и benchstat
«Мы уповаем на Бога. Все остальные обязаны предоставить данные».
— Уильям Эдвардс Деминг«Бенчмарк одной версии программы — это просто занятный анекдот; статистически подтвержденное сравнение — это уже инженерия».
— Брайан Керниган
Когда разработчик с гордостью заявляет в Pull Request: «Я переписал этот модуль, теперь он работает на 20% быстрее!» — опытный Senior-инженер всегда задает три неудобных вопроса:
- Быстрее по сравнению с чем именно?
- Каков доверительный интервал и какова вероятность того, что эти 20% — просто случайный шум разогретого процессора?
- Что произошло с аллокациями памяти и хвостовыми задержками?
Одиночный замер производительности в отрыве от контекста не имеет никакого практического смысла. Скорость — понятие сугубо относительное. Завершающий и самый ответственный этап инженерного цикла Measure → Profile → Optimize → Verify, сформулированного в 1. Обзор раздела. Как мыслить о производительности, — это доказательное сравнение двух состояний системы.
Мы уже умеем писать надежные бенчмарки (2. Benchmarking в Go), обходить коварные ловушки оптимизатора SSA (4. Подводные камни benchmark тестов), стабилизировать физическое окружение Linux (6. Стабилизация результатов) и просвечивать код профилировщиком (7. Профилирование внутри benchmark). В этой завершающей лекции блока мы замкнем контур: освоим математический аппарат доказательного сравнения, научимся читать дифференциальные профили pprof -diff_base, внедрим автоматический контроль регрессий в CI и разберемся, почему вердикт утилиты benchstat должен быть законом для любого разработчика.
Зачем сравнивать версии
Сравнение версий решает две фундаментальные задачи промышленной разработки:
- Верификация оптимизаций: Доказать математически, что внедренное усложнение кода (например, переход на низкоуровневые указатели
unsafeили пулsync.Pool) действительно приносит пользу, оправдывающую рост когнитивной сложности. - Предотвращение незаметных регрессий (Performance Regression): Защитить кодовую базу от случайной деградации, когда безобидный на первый взгляд рефакторинг в соседнем пакете роняет общую пропускную способность сервиса на 15%.
Senior-инженер никогда не говорит: «Мне кажется, стало быстрее». Он формулирует строгий метрологический вердикт:
«Изменение алгоритма в PR #412 дало ускорение на 17.89% ± 1.2% с доверительной вероятностью 99.9% (p = 0.000) и снизило аллокации памяти со 128 B/op до 0 B/op при n = 15+15».
Базовые принципы сравнения
Доказательный бенчмаркинг опирается на три непреложных закона научного эксперимента:
flowchart TD
classDef process fill:#1e293b,stroke:#38bdf8,stroke-width:1px,color:#f8fafc
classDef success fill:#064e3b,stroke:#34d399,stroke-width:1px,color:#f8fafc
classDef error fill:#7f1d1d,stroke:#f87171,stroke-width:1px,color:#f8fafc
classDef warning fill:#78350f,stroke:#fbbf24,stroke-width:1px,color:#f8fafc
classDef entry fill:#1e3a5f,stroke:#38bdf8,stroke-width:2px,color:#f8fafc
classDef aux fill:#334155,stroke:#64748b,stroke-width:1px,color:#f8fafc
classDef system fill:#312e81,stroke:#a78bfa,stroke-width:1px,color:#f8fafc
classDef data fill:#132d29,stroke:#2dd4bf,stroke-width:1px,color:#f8fafc
classDef network fill:#881337,stroke:#fb7185,stroke-width:1px,color:#f8fafc
classDef accent fill:#1e1b4b,stroke:#818cf8,stroke-width:1px,color:#f8fafc
subgraph P1["Аксиома 1: Ceteris Paribus (При прочих равных)"]
A1["Один код — одна переменная:<br>Меняется ТОЛЬКО тестируемый алгоритм.<br>Железо, ОС, компилятор и фоновая нагрузка тождественны."]:::system
end
subgraph P2["Аксиома 2: Статистическая строгость"]
A2["Разница средних — не доказательство:<br>Обязателен расчет доверительного интервала (stddev)<br>и статистической значимости p-value через Welch / Mann-Whitney."]
end
subgraph P3["Аксиома 3: Полнота метрик"]
A3["Комплексный анализ системы:<br>Контролируем ns/op, B/op, allocs/op, размер бинарника<br>и влияние на хвостовые квантили (p99)."]
end
P1 --> P2 --> P3
- Один код — одна переменная (Ceteris Paribus).
Сравниваются строго две версии бинарного файла, собранные одной и той же версией компилятора Go (go version), запущенные на одной и той же физической машине, с одинаковой фиксированной частотой процессора и губернаторомperformance. Если вы запустили базовый замер в пятницу на офисном сервере, а оптимизированный в понедельник на домашнем ноутбуке — ваши результаты можно сразу отправлять в мусорную корзину. - Статистическая строгость (Гипотеза $H_0$).
Разница средних арифметических — это иллюзия. Единственный научный критерий — проверка нулевой гипотезы $H_0$ («истинной разницы между версиями нет, а наблюдаемое отклонение вызвано случайным шумом»). Если вероятность случайности (p-value) превышает 0.05 (5%), изменение отвергается как статистически недостоверное. - Сравниваем не только среднее, но и распределение.
Улучшение среднего времени (p50) не гарантирует отсутствия деградации на хвостах (5. microbenchmarks vs macrobenchmarks). Если алгоритм в среднем стал быстрее, но стандартное отклонение выросло с 2% до 18% — в реальном продакшене это вызовет взрывной рост задержек на перцентилях p95 и p99 (6. Метрики. p50, p95, p99, 7. Tail latency и почему она важна).
Инструменты сравнения
Для сравнения версий в экосистеме Go существует специализированный стек инструментов:
flowchart LR
classDef process fill:#1e293b,stroke:#38bdf8,stroke-width:1px,color:#f8fafc
classDef success fill:#064e3b,stroke:#34d399,stroke-width:1px,color:#f8fafc
classDef error fill:#7f1d1d,stroke:#f87171,stroke-width:1px,color:#f8fafc
classDef warning fill:#78350f,stroke:#fbbf24,stroke-width:1px,color:#f8fafc
classDef entry fill:#1e3a5f,stroke:#38bdf8,stroke-width:2px,color:#f8fafc
classDef aux fill:#334155,stroke:#64748b,stroke-width:1px,color:#f8fafc
classDef system fill:#312e81,stroke:#a78bfa,stroke-width:1px,color:#f8fafc
classDef data fill:#132d29,stroke:#2dd4bf,stroke-width:1px,color:#f8fafc
classDef network fill:#881337,stroke:#fb7185,stroke-width:1px,color:#f8fafc
classDef accent fill:#1e1b4b,stroke:#818cf8,stroke-width:1px,color:#f8fafc
GoTest["go test -bench -count=N -benchmem"]:::process --> RawFiles["Текстовые отчеты (old.txt, new.txt)"]
RawFiles --> Benchstat["benchstat old.txt new.txt<br>(Статистический анализ дельты)"]
ProfTest["go test -bench -cpuprofile -memprofile"]:::process --> ProfFiles["Профили (old.prof, new.prof)"]
ProfFiles --> DiffPprof["go tool pprof -diff_base<br>(Анатомический дифференциальный анализ)"]
1. benchstat — основной инструмент
Официальный инструмент Go-сообщества для статистической оценки результатов бенчмарков — утилита benchstat (разрабатываемая в репозитории golang.org/x/perf/cmd/benchstat):
# Установка стабильной версии benchstat
go install golang.org/x/perf/cmd/benchstat@latest
Утилита принимает на вход два или более текстовых файла с выводом команды go test -bench (с обязательным флагом многократного запуска -count):
# 1. Снимаем выборку старой версии (baseline)
go test -bench=. -count=10 > old.txt
# 2. Вносим изменения в код...
# 3. Снимаем выборку новой версии
go test -bench=. -count=10 > new.txt
# 4. Проводим математический анализ
benchstat old.txt new.txt
Пример вывода benchstat:
name old time/op new time/op delta
Foo-8 12.3µs ± 2% 10.1µs ± 1% -17.89% (p=0.000 n=10+10)
Bar-8 5.6µs ± 5% 5.8µs ± 6% +3.57% (p=0.234 n=10+10)
Разбор вердикта:
Foo-8: Статистически безупречная победа. Среднее время упало с 12.3 мкс до 10.1 мкс (-17.89%). Доверительные интервалы узки (± 2%и± 1%), аp=0.000свидетельствует о том, что вероятность случайного совпадения практически равна нулю. Изменение безоговорочно принимается!Bar-8: Кажущееся замедление на 3.57% на самом деле является фикцией.p=0.234означает, что с вероятностью 23.4% эта дельта вызвана случайными колебаниями планировщика ОС или таймеров. Доверительный интервал включает нулевое изменение. Разницу списывают на шум, коммит не отклоняется и не принимается как замедление.
Кроме процессорного времени benchstat автоматически анализирует таблицы памяти: B/op (выделенные байты) и allocs/op (число обращений к аллокатору).
2. go test -bench с флагами профилирования
Если benchstat констатирует факт изменения времени, то связка профилировщиков с флагом pprof -diff_base (7. Профилирование внутри benchmark) вскрывает его анатомию. Вычитая базовый профиль из оптимизированного, инженер наглядно видит, в каких именно строках кода высвободились такты CPU или байты кучи.
3. Собственные скрипты и CI
В пайплайнах непрерывной интеграции (5. Performance regression detection) автоматизируют полный цикл:
- Запуск
go test -benchна PR. - Сравнение с эталонным файлом из ветки
main. - Автоматический расчет метрик через
benchstat. - Блокировка мерджа (Quality Gate) при превышении допустимого порога регрессии.
Пошаговая методология сравнения
Сравнение версий — это стандартизированный инженерный алгоритм, исключающий субъективные трактовки:
flowchart TD
classDef process fill:#1e293b,stroke:#38bdf8,stroke-width:1px,color:#f8fafc
classDef success fill:#064e3b,stroke:#34d399,stroke-width:1px,color:#f8fafc
classDef error fill:#7f1d1d,stroke:#f87171,stroke-width:1px,color:#f8fafc
classDef warning fill:#78350f,stroke:#fbbf24,stroke-width:1px,color:#f8fafc
classDef entry fill:#1e3a5f,stroke:#38bdf8,stroke-width:2px,color:#f8fafc
classDef aux fill:#334155,stroke:#64748b,stroke-width:1px,color:#f8fafc
classDef system fill:#312e81,stroke:#a78bfa,stroke-width:1px,color:#f8fafc
classDef data fill:#132d29,stroke:#2dd4bf,stroke-width:1px,color:#f8fafc
classDef network fill:#881337,stroke:#fb7185,stroke-width:1px,color:#f8fafc
classDef accent fill:#1e1b4b,stroke:#818cf8,stroke-width:1px,color:#f8fafc
A["1. Собрать эталонные результаты (baseline.txt)<br>go test -bench=. -count=10 -benchmem"]:::process --> B["2. Внести изменение в код (feature branch)"]
B --> C["3. Запустить бенчмарки в том же окружении<br>go test -bench=. -count=10 -benchmem > optimized.txt"]
C --> D["4. Запустить benchstat baseline.txt optimized.txt"]
D --> E{"p-value < 0.01?"}
E -- "Нет (p >= 0.01)" --> F["Изменение статистически незначимо!<br>Увеличить -count до 20-30 или стабилизировать ОС"]
E -- "Да (p < 0.01)" --> G{"Какова дельта времени / памяти?"}
G -- "Ускорение (delta < -3..5%)" --> H["✅ Успешная оптимизация!<br>Принять изменение"]
G -- "Замедление (delta > +3..5%)" --> I["❌ Регрессия производительности!<br>Отклонить PR или снять diff-профиль"]
G -- "В пределах порога (±2%)" --> F
1. Сбор эталона
Эталонный замер (baseline) снимается на чистой исходной ветке (обычно main или коммит до рефакторинга).
# Прячем незакоммиченные изменения или переключаемся на main
git stash
# Запускаем серию из 10-15 прогонов с контролем памяти
go test -bench=. -count=10 -benchmem > baseline.txt
2. Внесение изменения и замер
Возвращаем оптимизированный код и повторяем запуск с абсолютно теми же аргументами:
# Возвращаем изменения
git stash pop
# Запускаем в точно таких же условиях
go test -bench=. -count=10 -benchmem > optimized.txt
3. Статистическое сравнение
benchstat baseline.txt optimized.txt
4. Интерпретация результатов
При чтении отчета руководствуются строгими критериями:
p < 0.01и дельта отрицательна (ускорение > 3–5%):
Оптимизация доказана и успешна. Код принимается.p < 0.01и дельта положительна (замедление > 3–5%):
Зафиксирована регрессия. Изменение блокируется, начинается профилирование.p >= 0.05:
Результат статистически не отличим от нуля. Дельта утонула в шуме. Действия:- Либо увеличить выборку до
-count=20..30. - Либо стабилизировать процессор (отключить Turbo Boost, см. 6. Стабилизация результатов).
- Либо признать, что гипотеза об оптимизации не подтвердилась.
- Либо увеличить выборку до
- Высокая дисперсия (
stddev > ±10%):
Если в колонке разброса стоят двузначные числа, доверять результату нельзя независимо от значенияdelta. Среда измерения загрязнена сторонними процессами.
Сравнение профилей CPU и памяти
Когда benchstat сообщает об ускорении на 30%, мы хотим убедиться, что выигрыш получен именно там, где планировалось, и не вызвал скрытых проблем в других подсистемах.
Для этого применяется дифференциальное профилирование (-diff_base):
# 1. Замер CPU-профиля на старой версии
git checkout main
go test -bench=BenchmarkTarget -cpuprofile=cpu_old.prof
# 2. Замер CPU-профиля на новой версии
git checkout feature-optimized
go test -bench=BenchmarkTarget -cpuprofile=cpu_new.prof
# 3. Визуализация разностного профиля в браузере
go tool pprof -http=:8080 -diff_base=cpu_old.prof cpu_new.prof
flowchart TD
classDef process fill:#1e293b,stroke:#38bdf8,stroke-width:1px,color:#f8fafc
classDef success fill:#064e3b,stroke:#34d399,stroke-width:1px,color:#f8fafc
classDef error fill:#7f1d1d,stroke:#f87171,stroke-width:1px,color:#f8fafc
classDef warning fill:#78350f,stroke:#fbbf24,stroke-width:1px,color:#f8fafc
classDef entry fill:#1e3a5f,stroke:#38bdf8,stroke-width:2px,color:#f8fafc
classDef aux fill:#334155,stroke:#64748b,stroke-width:1px,color:#f8fafc
classDef system fill:#312e81,stroke:#a78bfa,stroke-width:1px,color:#f8fafc
classDef data fill:#132d29,stroke:#2dd4bf,stroke-width:1px,color:#f8fafc
classDef network fill:#881337,stroke:#fb7185,stroke-width:1px,color:#f8fafc
classDef accent fill:#1e1b4b,stroke:#818cf8,stroke-width:1px,color:#f8fafc
subgraph DiffBase["Механика go tool pprof -diff_base"]
OldP["cpu_old.prof (Baseline)"]
NewP["cpu_new.prof (Optimized)"]
Sub["Вычитание: Delta = New - Old"]:::process
OldP --> Sub
NewP --> Sub
end
subgraph Visualization["Отображение в UI pprof"]
Red["Красный цвет (+Delta):<br>Функция стала потреблять БОЛЬШЕ времени/памяти"]
Green["Зеленый цвет (-Delta):<br>Функция стала потреблять МЕНЬШЕ ресурсов (Ускорение)"]
end
Sub --> Visualization
В интерактивном веб-интерфейсе pprof разностный граф и Flamegraph окрашиваются контрастными цветами:
- Красные узлы (
+): Функции, которые стали тяжелее в новой версии (новые аллокации, дополнительные проверки). - Зеленые узлы (
-): Функции, где процессорное время или память сократились.
Это позволяет сразу увидеть, куда переместились затраты: например, если время ушло из json.Unmarshal в runtime.mallocgc (сигнализируя о лишних аллокациях), или если в оптимизированной конкатенации зеленый блок runtime.memmove соседствует с красным узлом runtime.mallocgc.
Аналогично сравниваются профили динамической памяти:
go tool pprof -http=:8081 -diff_base=mem_old.prof mem_new.prof
При использовании -alloc_space разностный профиль прямо укажет функции, сократившие давление на сборщик мусора.
Что сравнивать, кроме времени и аллокаций
Опытный бэкенд-инженер оценивает влияние изменений шире, чем банальные ns/op и B/op:
+-------------------------------------------------------------------------+
| Инженерная матрица сравнения версий |
+------------------------------------+------------------------------------+
| Метрика | Инструмент контроля |
+------------------------------------+------------------------------------+
| Время выполнения (ns/op) | go test -bench, benchstat |
| Память и аллокации (B/op, allocs) | go test -benchmem, memprofile |
| Размер бинарного файла | ls -l, go version -m <binary> |
| Аппаратные инструкции (IPC) | perf stat -e instructions:u |
| Кэш-промахи (Cache Misses) | perf stat -e cache-misses:u |
| Количество горутин рантайма | runtime.NumGoroutine(), trace |
| Хвостовые квантили (p95, p99) | Сквозные макротесты (k6, vegeta) |
+------------------------------------+------------------------------------+
- Размер скомпилированного бинарника:
Агрессивный инлайнинг и кодогенерация могут раздуть бинарник на десятки мегабайт, что ухудшит утилизацию кэша инструкций L1I. Проверяйте размер исполняемого файла черезls -lили метаданные черезgo version -m <binary>. - Число выполненных инструкций CPU:
С помощью системной утилиты Linuxperf stat -e instructions:u ./binaryможно замерить чистое количество машинных команд. Если время выполнения осталось прежним, но число инструкций сократилось на 30%, это означает, что код стал чище, но уперся в задержку памяти (Memory Bound). - Промахи кэша процессора (Cache Misses):
Замер черезperf stat -e cache-misses:u ./binaryпозволяет оценить дружественность структуры данных к аппаратному кэшу (8. Cache friendliness). - Конкурентные утечки:
Контроль количества горутин черезruntime.NumGoroutine()до и после бенчмарка страхует от утечек фоновых горутин-зомби.
Автоматизация в CI/CD
Ручное сравнение бенчмарков неизбежно забывается в суете спринтов. Надежный барьер против деградации — автоматический запуск benchstat в конвейере CI/CD (5. Performance regression detection).
sequenceDiagram
autonumber
actor Dev as Разработчик
participant Git as GitHub / GitLab
participant CI as Выделенный Bare-Metal Runner
participant Store as Хранилище эталонов (Git LFS / S3)
Dev->>Git: Push Pull Request
Git->>CI: Триггер Pipeline
CI->>Store: Загрузка baseline.txt для ветки main
CI->>CI: Сборка и запуск: go test -bench=. -count=10 > pr.txt
CI->>CI: Расчет: benchstat baseline.txt pr.txt
alt Обнаружена регрессия (delta > +5%, p < 0.01)
CI-->>Git: ❌ Quality Gate FAILED: Регрессия в функции Foo!
Git-->>Dev: Блокировка мерджа с публикацией diff-отчета
else Производительность в норме или улучшена
CI-->>Git: ✅ Quality Gate PASSED
Git-->>Dev: Разрешение на слияние PR
Note over Git,Store: При мерже в main файл baseline.txt обновляется
end
Стандартный архитектурный регламент CI-бенчмаркинга:
- В репозитории (в каталоге
testdata/benchmarks/baseline.txt) или внешнем S3-хранилище сохраняется утвержденный эталон производительности веткиmain. - Для каждого Pull Request изолированный раннер компилирует проект и снимает свежие результаты:
go test -bench=. -count=10 > pr.txt. - Запускается валидатор:
benchstat testdata/benchmarks/baseline.txt pr.txt. - Если
benchstatфиксирует деградацию ключевых функций более чем на 5% приp-value < 0.05, пайплайн падает с ошибкой, а в PR автоматически публикуется форматированная таблица изменений. - При слиянии оптимизирующего PR в ветку
mainфайлtestdata/benchmarks/baseline.txtавтоматически обновляется новыми эталонными замерами.
Mechanical Sympathy: почему контекст окружения важен при сравнении
Почему нельзя слепо верить числам, полученным на разных машинах?
Микроархитектура современных чипов крайне чувствительна к контексту:
- Размер и топология кэшей: У процессора AMD Ryzen 7950X кэш L3 составляет 64 МБ, разделенных между двумя кристаллами CCD. У Intel Core i9-14900K — монолитный кэш L3 на 36 МБ с кольцевой шиной Ring Bus. Алгоритм, летающий на одном процессоре за счет огромного кэша, на другом чипе начнет массово выпадать в память.
- Выравнивание инструкций по границе 64 байт: Небольшое изменение в длине одной вспомогательной функции в начале файла может сдвинуть адреса горячего цикла в машинном коде. Если цикл случайно пересечет границу 64-байтной строки кэша инструкций L1I, процессор будет вынужден декодировать две кэш-линии вместо одной, замедлив выполнение на 5–10% на абсолютно идентичном коде!
- Версия рантайма Go: С каждым новым релизом (Go 1.21, 1.22, 1.23, 1.24) команда Go оптимизирует планировщик, структуру хэш-таблиц (Swiss Tables), инлайнер и GC. Сравнение кода на разных версиях компилятора сравнивает работу компилятора, а не ваш код.
Золотое правило: Сравнивать допустимо только относительную дельту ($\Delta$) двух версий, полученную в едином, строго стабилизированном физическом эксперименте.
Пример полного цикла сравнения
Разберем завершенный практический пример: оптимизацию функции копирования буферов.
1. Эталонная реализация с постоянными аллокациями
package bench_test
import "testing"
var resultSlice []byte
func BenchmarkCopy(b *testing.B) {
src := make([]byte, 4096)
b.ResetTimer()
for i := 0; i < b.N; i++ {
// ОШИБКА: Выделение нового слайса 4 КБ в куче на каждой итерации
dst := make([]byte, len(src))
copy(dst, src)
resultSlice = dst
}
}
Запуск замера базовой версии:
go test -bench=BenchmarkCopy -count=15 -benchmem > old.txt
2. Оптимизированная версия с пулом буферов sync.Pool
Инженер устраняет аллокации, переиспользуя буферы через пул памяти:
var bufferPool = sync.Pool{
New: func() any {
b := make([]byte, 4096)
return &b
},
}
func BenchmarkCopy(b *testing.B) {
src := make([]byte, 4096)
b.ResetTimer()
for i := 0; i < b.N; i++ {
bufPtr := bufferPool.Get().(*[]byte)
dst := *bufPtr
copy(dst, src)
resultSlice = dst
bufferPool.Put(bufPtr)
}
}
Запуск замера оптимизированной версии:
go test -bench=BenchmarkCopy -count=15 -benchmem > new.txt
3. Математический арбитраж через benchstat
benchstat old.txt new.txt
Терминальный вывод утилиты:
name old time/op new time/op delta
Copy-8 1.09µs ± 3% 0.32µs ± 4% -70.64% (p=0.000 n=15+15)
name old alloc/op new alloc/op delta
Copy-8 4.10KB ± 0% 0.00KB -100.00% (p=0.000 n=15+15)
name old allocs/op new allocs/op delta
Copy-8 1.00 ± 0% 0.00 -100.00% (p=0.000 n=15+15)
Математический вердикт:
- Время (
time/op): Сократилось более чем в 3.4 раза (с 1.09 мкс до 0.32 мкс, выигрыш-70.64%). - Память (
alloc/op): Выделение 4.10 КБ на операцию полностью сведено к 0.00 KB (-100.00%). - Число аллокаций (
allocs/op): Упало с 1 до 0. - Статистическая надежность: Дисперсия внутри групп минимальна (
± 3%и± 4%), аp=0.000при выборкеn=15+15гарантирует абсолютную математическую достоверность.
Этот Pull Request принимается в кодовую базу с наивысшим баллом инженерного качества.
Итог
- Сравнение версий — фундамент доказательной оптимизации: Одиночный замер — это непроверенная гипотеза; только парное сравнение с эталоном доказывает пользу инженерных изменений.
benchstat— высший суд производительности: Инструмент автоматически рассчитывает средние, доверительные интервалы (stddev) и статистическую достоверность (p-value). Любые изменения сp >= 0.05отвергаются как шум.- Дифференциальное профилирование (
pprof -diff_base): Позволяет визуализировать точечный баланс изменений: куда сместились процессорные такты и аллокации памяти между двумя коммитами. - Комплексность метрик: Оценивайте не только наносекунды, но и аллокации (
B/op,allocs/op), размер бинарного файла и поведение на хвостовых квантилях задержек (p99). - Автоматический барьер в CI: Включение тестов производительности в пайплайн CI с контролем относительно утвержденного файла эталонов навсегда закрывает дверь скрытым регрессиям.
Завершение подраздела «02. Benchmarking»
Этой лекцией мы полностью закрыли фундаментальный подраздел «Benchmarking». Мы прошли путь от понимания физических законов измерений до тонкостей адаптивного подбора b.N, борьбы со случайным системным шумом, рентгеновской диагностики профилировщиком и строгой математической статистики benchstat.
Теперь наш исследовательский чемодан укомплектован эталонными метрологическими инструментами. Мы переходим от вопроса «сколько времени занимает код?» к глубокому погружению в механизмы процессора: в следующий подраздел «03. CPU profiling», где первой фундаментальной темой станет 1. pprof. Введение.