Экспериментальный стенд на Go: один и тот же сценарий обработки HTTP-запроса реализован четырьмя способами, чтобы сравнить влияние приёмов оптимизации на память, аллокации и задержку. Проект задуман как основа для доклада, статей и воспроизводимых замеров — не как production-сервис.
- Клиент шлёт POST с JSON-массивом «событий» (метрики).
- Сервер фильтрует записи по порогу
value > 50, считает count, sum, mean, p50, p95, p99 и отвечает JSON. - Четыре эндпоинта отличаются только реализацией парсинга/буферов/структур данных (см. таблицу ниже).
| Эндпоинт | Идея |
|---|---|
POST /v1/aggregate |
«Как в туториале»: encoding/json, декодер с тела запроса, без пулов. |
POST /v2/aggregate |
Пулы sync.Pool для буфера и слайсов, Unmarshal/Marshal с переиспользованием. |
POST /v3/aggregate |
Плоские структуры без указателей в горячих данных, интернирование имён, unsafe для копирования id; меньше работы для GC при сканировании. |
POST /v4/aggregate |
Sonic (github.com/bytedance/sonic) + плоские структуры — быстрый JSON без стандартного encoding/json на горячем пути. |
Дополнительно в репозитории есть скрипты для нагрузки (vegeta), микробенчмарков (go test -bench) и HTML-отчёта с графиками.
Нужен Go 1.21+ (в go.mod указана используемая версия).
git clone <repo-url>
cd <каталог-репозитория>Без этих файлов бенчи пропустятся или упадут.
go run ./bench/gen -out bench/payloadsПоявятся payload_100.json, payload_1k.json, payload_10k.json, payload_50k.json.
go run ./cmd/server -addr :8080 -out results -version allЭндпоинты: /v1/aggregate … /v4/aggregate. Метрики в фоне пишутся в results/metrics_*.jsonl (если нужно).
Полезно для отладки:
GET /debug/metrics— снимокruntime.MemStatsв JSON.GET /debug/pprof/— стандартный pprof (профиль для PGO снимать отсюда же).
Нужен установленный vegeta. На Linux/macOS:
bash scripts/load_test.shНа Windows смотрите параметры в scripts/load_test.sh и адаптируйте под PowerShell или WSL.
Из корня репозитория (UTF-8 вывод важен для benchstat и генератора отчёта на Windows — см. ниже).
Один прогон (PowerShell):
go run ./bench/gen -out bench/payloads
go test ./bench/ -bench=BenchmarkHandlers -benchmem -count=10 -timeout=900s 2>&1 |
Out-File -FilePath results/bench_run.txt -Encoding utf8Статистика двух файлов в консоли (benchstat):
go install golang.org/x/perf/cmd/benchstat@latest
benchstat results/run1.txt results/run2.txtТипичный сценарий — сравнить тот же код на Windows и в Linux-контейнере (например, чтобы увидеть разницу по Sonic). Нужны Docker и этот репозиторий на диске.
- Windows (локально): пейлоады и бенч, результат в UTF-8:
go run ./bench/gen -out bench/payloads
go test ./bench/ -bench=BenchmarkHandlers -benchmem -count=10 -timeout=900s 2>&1 |
Out-File -FilePath results/bench_windows.txt -Encoding utf8(В Windows PowerShell 5.1 у Tee-Object нет параметра -Encoding; для вывода в UTF-8 используйте Out-File -Encoding utf8 или PowerShell 7+.)
- Linux внутри Docker — образ
Dockerfileоснован наgolang:bookwormиGOTOOLCHAIN=auto, чтобы при необходимости подтянуть версию Go изgo.mod(например 1.25.x), даже если в базовом образе Go чуть старше. Пейлоады создаются при сборке образа.
.\scripts\bench_docker.ps1Или в Git Bash / WSL:
bash scripts/bench_docker.shПо умолчанию вывод сохраняется в results/bench_linux.txt (путь можно передать первым аргументом скрипта).
- HTML-отчёт с таблицей A / B / Δ и графиками по прогону A (
-in):
go run ./bench/report/ -in results/bench_windows.txt -cmp results/bench_linux.txt -out results/report_win_vs_linux.htmlЕсли нужны графики именно по Linux, поменяйте местами: -in results/bench_linux.txt -cmp results/bench_windows.txt.
Имя образа можно задать переменной GOSTAND_BENCH_IMAGE.
Собирает интерактивные графики (Chart.js с CDN) и фиксирует среду на момент генерации: версия Go, GOOS/GOARCH, GOMAXPROCS, версия зависимости Sonic, имя хоста.
Только один прогон (графики и «прогон A» в таблице):
go run ./bench/report/ -in results/bench_run.txt -out results/report.htmlДва прогона — таблица с колонками A / B / Δ и отдельный график Δ по времени для размера 1k:
go run ./bench/report/ -in results/before.txt -cmp results/after.txt -out results/report.htmlСмысл Δ: ((B - A) / A \cdot 100%). Для времени и аллокаций отрицательный процент означает улучшение во втором прогоне.
На Windows перенаправление вывода в файл иногда пишет UTF-16 — тогда
benchstatи парсер отчёта видят «пустой» файл. ИспользуйтеOut-File -Encoding utf8в PowerShell илиteeс UTF-8 в bash.
Библиотека Sonic ориентирована на максимальную скорость в типичной серверной среде Linux amd64 (и частично другие Unix): там задействуется нативное / SIMD-ускоренное ядро парсера там, где это поддерживается сборкой.
На Windows чаще используется запасной (совместимый) путь без того же низкоуровневого ядра, что на Linux. В результате:
- V4 по-прежнему может быть существенно быстрее, чем «голый»
encoding/json, но цифры не переносятся один в один с Linux. - Для честного сравнения «как в продакшене» имеет смысл прогнать те же бенчмарки в Linux (железо, VM, WSL2, Docker) и указать ОС в отчёте — блок «Среда» в HTML это упрощает.
PGO в Go — сборка всего бинарника с профилем CPU, а не отдельной функции. Типичный сценарий:
- Собрать бинарник без PGO, запустить под нагрузкой.
- Снять профиль:
curl -o cpu.prof "http://localhost:8080/debug/pprof/profile?seconds=60"(пока идёт нагрузка). - Положить профиль как
cmd/server/default.pgo(или указать-pgo=...при сборке). - Пересобрать и сравнить задержки/бенчи с предыдущей сборкой.
Сравнение двух прогонов бенчмарка удобно оформлять через benchstat или через go run ./bench/report/ -in ... -cmp ....
Подробности и параметры компилятора — в документации Go: Profile-guided optimization.
Dockerfile образ для прогона бенчмарков под Linux
cmd/server/ точка входа HTTP-сервера
internal/handler/ v1–v4 обработчики
internal/model/ типы событий и ответа
internal/aggregate/ фильтрация и перцентили
internal/collector/ фоновый снимок MemStats (JSONL)
bench/ бенчмарки и генератор пейлоадов
bench/report/ генератор HTML-отчёта (шаблон report.html.tmpl)
scripts/ vegeta, Docker-бенч, анализ
pgo/ каталог для cpu-профилей (артефакты не коммитятся)
results/ локальные результаты замеров (в .gitignore)