Перейти к основному содержимому

Профилирование, trace и fuzz в Go

Разработчику Архитектору

Go поставляет профилирование и трассировку в toolchain — отдельный APM для первичной диагностики не обязателен. Статья развивает тестирование и механику языка.

См. также: Асинхронность и горутины · Рекомендации по разработке · Сборка мусора.


Порядок работы с производительностью

  1. Корректность — тесты, -race.
  2. Benchmarkgo test -bench, сравнение до/после.
  3. Профиль — CPU, heap, goroutine через pprof.
  4. Trace — задержки, блокировки, планировщик.
  5. Оптимизация — точечно, с повторным 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-порт — см. 30.md.

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-функции (документация + тест) — см. 192.md; 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 — гонки данных.
  • Профилируйте под реалистичной нагрузкой, не на пустом сервере.

Мини-практикум

  1. Добавьте -benchmem к существующему benchmark в проекте.
  2. Снимите CPU profile при нагрузке на Gin-сервис; найдите top-3 функции.
  3. Напишите Fuzz* для парсера, который принимает строку с числом.

Типичные ошибки

ОшибкаПоследствие
Benchmark с time.Sleep внутриЗамеряется sleep, а не код
Один прогон -benchСлучайные выводы
pprof на dev без нагрузкиПустой или misleading profile
Fuzz без seed f.AddДольше сходится к интересным кейсам

Связанные материалы


Основа по протоколу

Базовый разбор HTTP — HTTP как основа веб-интеграций.

См. также

Другие статьи этого же раздела в боковом меню (как на странице "О разделе").