Профилирование, trace и fuzz в Go
Go поставляет профилирование и трассировку в toolchain — отдельный APM для первичной диагностики не обязателен. Статья развивает тестирование и механику языка.
См. также: Асинхронность и горутины · Рекомендации по разработке · Сборка мусора.
Порядок работы с производительностью
- Корректность — тесты,
-race. - Benchmark —
go test -bench, сравнение до/после. - Профиль — CPU, heap, goroutine через
pprof. - Trace — задержки, блокировки, планировщик.
- Оптимизация — точечно, с повторным benchmark.
Сначала измерение, потом правка. Переписывание main() для тестируемости делает benchmark и pprof осмысленными.
Benchmark
func BenchmarkParseLines(b *testing.B) {
data := []byte("line1\nline2\nline3\n")
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = parseLines(data)
}
}
go test -bench=BenchmarkParseLines -benchmem ./...
Разбор:
b.Nподбирает runtime; внутри цикла — только измеряемый код.-benchmemпоказывает аллокации на операцию — частый источник регрессий.ReportAllocs()добавляет статистику alloc/op в вывод.
benchstat
Установка: go install golang.org/x/perf/cmd/benchstat@latest.
go test -bench=. -count=10 ./pkg/... > old.txt
# ... правки ...
go test -bench=. -count=10 ./pkg/... > new.txt
benchstat old.txt new.txt
Разбор:
-count=10сглаживает шум OS и CPU frequency scaling.benchstatсчитает p-value и выводит "+5.2%" только если разница статистически заметна.- Без benchstat легко принять случайный выигрыш за оптимизацию.
CPU-профиль (pprof)
Из теста
go test -cpuprofile=cpu.prof -bench=BenchmarkParseLines ./...
go tool pprof cpu.prof
В интерактивном режиме — top, list parseLines, web (нужен graphviz).
HTTP-сервис
import _ "net/http/pprof"
func main() {
go func() {
log.Println(http.ListenAndServe("localhost:6060", nil))
}()
// ... ваш сервер ...
}
go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30
Разбор:
- Blank import регистрирует
/debug/pprof/*на DefaultServeMux. profile?seconds=30собирает CPU-sample во время реальной нагрузки.- В production endpoint изолируют firewall или отдельный admin-порт — см. Механика языка и гонки данных.
Heap и goroutine
| Профиль | URL / флаг | Вопрос |
|---|---|---|
| Heap | /debug/pprof/heap | Кто аллоцирует память |
| Goroutine | /debug/pprof/goroutine | Утечки горутин |
| Block | /debug/pprof/block | Блокировки на mutex/channel |
| Mutex | /debug/pprof/mutex | Контention мьютексов |
go tool pprof http://localhost:6060/debug/pprof/heap
Разбор:
- Heap показывает in-use и allocations; для утечек смотрят рост между двумя снимками.
- Goroutine profile полезен, когда счётчик горутин растёт без остановки — часто забытый
goбез exit.
go tool trace
go test -trace=trace.out -bench=BenchmarkParseLines ./...
go tool trace trace.out
Для HTTP-сервера — runtime/trace или net/http/pprof endpoint /debug/pprof/trace.
Разбор:
- Trace показывает timeline горутин, GC, syscall, блокировки — то, что не видно в aggregate CPU profile.
- Полезен, когда latency высокая, а CPU profile пустой (ожидание I/O).
Fuzzing (Go 1.18+)
Fuzz-тест генерирует входные данные и ищет падения и несоответствия:
func FuzzParseAge(f *testing.F) {
f.Add("21")
f.Add("0")
f.Fuzz(func(t *testing.T, s string) {
age, err := parseAge(s)
if err != nil {
return
}
if age < 0 || age > 150 {
t.Fatalf("age out of range: %d", age)
}
})
}
go test -fuzz=FuzzParseAge -fuzztime=30s ./...
Разбор:
f.Add— seed-кorpus; fuzzer мутирует строки и сохраняет crashing input вtestdata/fuzz/.- Fuzz дополняет table-driven тесты на неожиданных входах (Unicode, длинные строки).
- Не заменяет
-raceи не доказывает корректность — только находит краевые сбои.
go:generate и примеры
Директива go:generate:
//go:generate stringer -type=Status
type Status int
go generate ./...
Example-функции (документация + тест) — см. Тестирование в Go; testing/quick — случайные значения для property-подобных проверок без полноценного fuzz.
Кросс-компиляция
GOOS=linux GOARCH=amd64 go build -o app ./cmd/app
Разбор:
- Go кросс-компилирует из коробки; CGO усложняет (нужен cross-C-compiler).
- В CI matrix собирают бинарники под несколько платформ; benchmark гоняют на одной эталонной машине.
Недостижимый код
go test -coverprofile=cover.out ./...
go tool cover -func=cover.out
Пакет golang.org/x/tools/go/analysis/passes/unreachable и staticcheck находят мёртвые ветки. Удаление unreachable-кода упрощает профили — меньше шума в list.
Ключевые тезисы
- Benchmark + benchstat — доказательство, что оптимизация реальна.
- pprof отвечает на "где CPU/память"; trace — на "кто ждал и почему".
- Fuzz ловит краевые входы;
-race— гонки данных. - Профилируйте под реалистичной нагрузкой, не на пустом сервере.
Мини-практикум
- Добавьте
-benchmemк существующему benchmark в проекте. - Снимите CPU profile при нагрузке на Gin-сервис; найдите top-3 функции.
- Напишите
Fuzz*для парсера, который принимает строку с числом.
Типичные ошибки
| Ошибка | Последствие |
|---|---|
Benchmark с time.Sleep внутри | Замеряется sleep, а не код |
Один прогон -bench | Случайные выводы |
| pprof на dev без нагрузки | Пустой или misleading profile |
Fuzz без seed f.Add | Дольше сходится к интересным кейсам |
Связанные материалы
- Тестирование в Go — unit, httptest, CI
- Механика языка и гонки данных —
-race, pprof endpoint - Особенности языка Go — GC и аллокации
- Следующий шаг — CLI на cobra и viper
Базовый разбор HTTP — HTTP как основа веб-интеграций.