Перейти к содержимому
ГоМастер

Поиск по урокам

Найдите урок, курс, термин или раздел

Как читать профиль pprof в Go

pprof входит в стандартную поставку Go, но разобраться в его выводе с нуля непросто: профилей шесть, у каждого своя логика, а команды отличаются от учебников. Здесь каждый тип профиля разобран по шагам: как собрать, как открыть, как читать строки вывода и что обычно находят.

6 типов профилейкоманды и разбор вывода

Профилирование решает споры о медленном коде

Спор «здесь медленно» или «здесь нормально» в Go решается буквально: профиль показывает, где именно уходит время, память или потоки. Это дешевле, чем читать код и гадать, и точнее, чем интуиция: очень часто «медленное место» оказывается не тем, где все думают.

Инструмент встроен в стандартную библиотеку, поэтому ничего ставить не нужно. В бенчмарках профиль снимается флагом, в работающем сервисе — несколькими строками кода или готовым эндпоинтом net/http/pprof. Дальше файл профиля открывается командой go tool pprof: она работает и в терминале, и в браузере через -http.

Важная деталь, на которой спотыкаются чаще всего: не все профили включены по умолчанию. Для block и mutex нужно вручную вызвать SetBlockProfileRate и SetMutexProfileFraction в коде, иначе профиль будет пустым, и человек решит, что инструмент не работает. Ниже эти настройки указаны для каждого типа.

Ещё одно различие, которое стоит понимать сразу: бенчмарк и профиль живого сервиса отвечают на разные вопросы. Бенчмарк измеряет вашу функцию в идеальных условиях — без конкуренции, без кэша и без шума. Профиль сервиса показывает реальную картину, где время уходит на посторонние вещи: сериализацию, логирование, ожидание базы. Поэтому «медленный бенчмарк» и «медленный сервис» могут иметь совершенно разные причины, и профилировать нужно то, что тормозит у пользователя.

Какой профиль вам нужен

Профилей в Go несколько, и они отвечают на разные вопросы. Выберите тип — ниже появятся команды и разбор вывода.

Показывает, какие функции потребляют CPU. Ответ на вопрос «почему сервис ест ядра».

Шаг первый: собрать профиль

go test -bench=. -benchtime=30s -cpuprofile=cpu.out ./pkg/...

В сервисе профиль включают в коде: pprof.StartCPUProfile(f) при старте и pprof.StopCPUProfile() при остановке. Снимать нужно 30 секунд реальной нагрузки — на заглушке профиль покажет пустоту.

Шаг второй: посмотреть результат

go tool pprof -top -cum cpu.out
go tool pprof -http=:8080 cpu.out   # графический интерфейс

Шаг третий: прочитать вывод

Формат вывода pprof top — пример, иллюстрирующий разбор. В реальном профиле значения будут другими.

Пример вывода команды pprof top с колонками flat, cum и функцией
flatflat%cumcum%функция
1.21s31.42%1.21s31.42%regexp.(*machine).match
0.84s21.83%1.02s26.49%main.parseOrder
0.51s13.25%0.62s16.10%encoding/json.Decode
0.02s0.52%0.48s12.47%main.handleOrder

Как читать

  • Смотрите колонку cum (кумулятивно): она включает время вызванных функций. Именно cum показывает, сколько времени стоит ваш обработчик целиком.
  • flat — время внутри самой функции. Большой flat означает, что внутри тяжёлая работа; большой cum при маленьком flat — что функция просто много вызывает.
  • Сортируйте по cum (-top -cum), а не по flat: иначе вы увидите stdlib, а не свой код.
  • В графическом виде (go tool pprof -http) выбирайте граф и ищите широкие блоки — это дорогие участки.

Что обычно находят

  • Регулярные выражения в горячем пути: regexp медленнее простого сравнения и strings.Contains.
  • JSON-декодирование целых больших документов, когда нужны два поля.
  • Лишние аллокации внутри цикла, которые видны в графе как вызовы runtime.makeslice.

Подводные камни

  • вниманиеПрофиль показывает только то время, пока профайлер был включён: короткий замер в бенчмарке не отражает поведение сервиса.
  • вниманиеПрофиль без символов (сборка с -ldflags="-s -w") превращается в набор адресов — пересоберите бинарник с символами.
  • вниманиеПрофилирование меняет timings: включайте его в стенде, а не в проде с большой нагрузкой.
Общий порядок работы: сначала воспроизвести проблему нагрузкой, потом снять профиль, потом смотреть -top для верхушки списка и -list или -peek для конкретной функции. Оптимизировать по профилю, а не по ощущениям: иначе вы почти гарантированно выберете не то место.

Все типы профилей коротко

Подробный разбор каждого типа — в интерактивном блоке выше. Здесь краткая шпаргалка: что профиль показывает и как его снять.

Типы профилей pprof: что показывает и как снять
ПрофильЧто показываетКак снять
CPUПоказывает, какие функции потребляют CPU. Ответ на вопрос «почему сервис ест ядра».go test -bench=. -benchtime=30s -cpuprofile=cpu.out ./pkg/...
HeapПоказывает живые объекты в куче. Ответ на вопрос «что удерживает память».go test -bench=. -benchmem -memprofile=heap.out ./pkg/...
AllocsВсе аллокации за всё время, а не только живые объекты. Ловит мусор в горячем пути.go test -bench=. -benchmem -memprofile=mem.out ./pkg/...
GoroutineСнимок всех живых горутин с их стеками. Главный инструмент в поиске утечек горутин.go tool pprof -top goroutine.out
BlockВремя, проведённое в блокировках на каналах и мьютексах. Показывает contention.// в коде сервиса, до старта:
MutexСтатистика конкуренции за мьютексы: доля времени в ожидании и список самых горячих.runtime.SetMutexProfileFraction(100) // сэмплирование 1 из 100 конфликтов

Частые вопросы о профилировании в Go

С чего начать, если сервис тормозит?

С CPU-профиля, но только если есть нагрузка, похожая на боевую. Профиль без нагрузки покажет пустоту или фоновые задачи. Снимите 30 секунд профиля под нагрузкой, откройте go tool pprof -top -cum и посмотрите, какая функция стоит на первом месте по cum. Если узкое место в памяти — смотрите heap, если сервис упирается в горутины — goroutine-профиль.

Почему профиль block или mutex пустой?

Потому что их нужно включить в коде: runtime.SetBlockProfileRate и runtime.SetMutexProfileFraction. По умолчанию они выключены, чтобы не создавать накладных расходов в продакшене. Это самая частая причина разочарования: профайлер запустили, а данных нет.

Что означают flat и cum?

flat — время или память, потраченные внутри самой функции. cum — кумулятивно, включая всё, что она вызвала. Для поиска узкого места смотрите cum: он показывает полную стоимость вызова вместе с поддеревом. Если flat большой, а cum почти равен flat, проблема внутри функции; если flat мал, а cum велик, функция просто много вызывает.

Как искать утечку памяти?

Смотрите heap-профиль в режиме inuse_space: он показывает живые объекты. Если конкретная структура занимает растущую долю профиля, смотрите на её ссылки: скорее всего, её кладут в глобальную карту или в замыкание, откуда она не выходит. Помогает сравнение двух снимков профиля с интервалом в минуту: то, что растёт, и есть утечка.

Где изучить дальше