Профилирование Go кода: находим узкие места
Оптимизация производительности — одна из ключевых задач разработчика, особенно когда речь идет о высоконагруженных системах на Go. Однако прежде чем что-то оптимизировать, нужно точно знать, какие участки кода являются узкими местами. Для этого существует профилирование — сбор и анализ метрик выполнения программы.
В этой статье мы разберем, как профилировать Go-приложения с помощью встроенных инструментов: pprof и trace. Вы узнаете, как находить проблемные места по CPU, памяти, блокировкам и горутинам, а также как интерпретировать полученные данные.
Что такое профилирование и зачем оно нужно?
Профилирование — это процесс сбора данных о работе программы во время её выполнения. Эти данные помогают ответить на вопросы:
- Какие функции потребляют больше всего процессорного времени?
- Где происходит выделение большого количества памяти?
- Какие горутины блокируются и на чем?
- Сколько времени занимает системный вызов или операция ввода-вывода?
Go предоставляет мощные инструменты для профилирования прямо из коробки. Основные из них — net/http/pprof для веб-приложений и runtime/pprof для CLI-утилит.
Инструмент pprof: CPU и Memory профили
pprof — основной инструмент для профилирования Go. Он умеет собирать профили CPU, памяти, блокировок, горутин и мутекс-конкуренции.
Добавление pprof в веб-приложение
Самый простой способ подключить профилирование — импортировать пакет net/http/pprof:
package main
import (
"net/http"
_ "net/http/pprof" // регистрирует обработчики pprof
)
func main() {
// Ваше приложение
http.HandleFunc("/", handler)
http.ListenAndServe(":8080", nil)
}
После этого по адресу /debug/pprof/ будут доступны различные профили. Например:
/debug/pprof/profile— CPU профиль (по умолчанию 30 секунд)/debug/pprof/heap— профиль памяти (heap)/debug/pprof/goroutine— дамп всех горутин/debug/pprof/block— профиль блокировок
Сбор CPU профиля
Для сбора CPU профиля используйте команду:
go tool pprof http://localhost:8080/debug/pprof/profile?seconds=30
После завершения сбора откроется интерактивная консоль pprof. Самые полезные команды:
top— показать функции, потребляющие больше всего CPUlist <функция>— показать построчную разбивку времениweb— открыть граф вызовов в браузере (требуется Graphviz)peek <функция>— показать вызывающие и вызываемые функции
Пример вывода top:
Showing nodes accounting for 3.50s, 70% of 5.00s total
flat flat% sum% cum cum%
1.20s 24.00% 24.00% 1.20s 24.00% encoding/json.(*Decoder).Decode
0.80s 16.00% 40.00% 0.80s 16.00% runtime.mallocgc
0.60s 12.00% 52.00% 1.80s 36.00% main.processData
0.50s 10.00% 62.00% 0.50s 10.00% runtime.memmove
0.40s 8.00% 70.00% 0.40s 8.00% strings.ToLower
Сбор профиля памяти
Для анализа использования памяти:
go tool pprof http://localhost:8080/debug/pprof/heap
Важные команды в pprof для памяти:
top— функции, выделяющие больше всего памятиsample_index=alloc_space— переключиться на общее количество выделенной памятиsample_index=inuse_space— переключиться на текущую используемую памятьtree— показать дерево вызовов с размерами
Профилирование блокировок и горутин
Узкие места часто связаны не только с CPU, но и с блокировками или утечками горутин.
Профиль блокировок (block)
Для включения сбора блокировок необходимо установить долю выборки:
runtime.SetBlockProfileRate(1) // собирать все блокировки
Затем профиль доступен по адресу /debug/pprof/block. Анализ:
go tool pprof http://localhost:8080/debug/pprof/block
Профиль мутекс-конкуренции (mutex)
Аналогично для мутексов:
runtime.SetMutexProfileFraction(1)
// Сбор
go tool pprof http://localhost:8080/debug/pprof/mutex
Дамп горутин
Иногда нужно просто посмотреть, сколько горутин запущено и что они делают:
wget http://localhost:8080/debug/pprof/goroutine?debug=2 -O goroutines.txt
В файле будет текстовый дамп каждой горутины с стеком вызовов. Это помогает обнаружить утечки горутин, когда они "зависают" в ожидании.
Инструмент trace: визуализация выполнения
Пакет runtime/trace предоставляет более детальную картину выполнения программы: временные шкалы горутин, события GC, системные вызовы и т.д.
Запись трассировки
package main
import (
"os"
"runtime/trace"
)
func main() {
f, _ := os.Create("trace.out")
defer f.Close()
trace.Start(f)
defer trace.Stop()
// Ваш код
processData()
}
Для просмотра трассировки используйте команду:
go tool trace trace.outБлоги
Рекомендаций по книгам
Читайте также