При ежедневной разработке Golang иногда необходим анализ производительности программ, и Golang предоставляет встроенный pprof
инструмент, помогающий достичь этого.
Пакет pprof
это runtime/pprof
.
pprof
в основном используется для анализа производительности программы, включая следующее содержимое:
- Профилирование ЦП: отслеживает использование ЦП своими программами Go в соответствии с указанным сбором времени и может определить, в каком сегменте программы программа Go потребляет ЦП в течение длительного времени.
- Профилирование памяти: используется для анализа использования области стека памяти программой и для обнаружения утечек памяти.
- Профилирование горутины: параллельный анализ производительности, используемый для составления отчетов об операциях горутины и их количестве для текущей среды выполнения.
- Профилирование блоков. Егоможно использовать для записи количества времени, которое горутина тратит на ожидание общих ресурсов.
- Профилирование мьютексов.Профилирование мьютексов похоже на профилирование блоков, но оно регистрирует только ожидания или задержки, вызванные конфликтом блокировок.
Для нас наиболее часто используемыми методами профилирования являются CPU profiling
и Memory profiling
. Поэтому основное содержание этого блога будет сосредоточено на использовании этих двух методов.
Профилирование ЦП.
Чтобы записать производительность с помощью pprof
, просто вызовите pprof.StartCPUProfile(file)
в нужном месте.
func StartCPUProfile(w io.Writer) error { // The runtime routines allow a variable profiling rate, // but in practice operating systems cannot trigger signals // at more than about 500 Hz, and our processing of the // signal is not cheap (mostly getting the stack trace). // 100 Hz is a reasonable choice: it is frequent enough to // produce useful data, rare enough not to bog down the // system, and a nice round number to make it easy to // convert sample counts to seconds. Instead of requiring // each client to specify the frequency, we hard code it. const hz = 100 cpu.Lock() defer cpu.Unlock() if cpu.done == nil { cpu.done = make(chan bool) } // Double-check. if cpu.profiling { return fmt.Errorf("cpu profiling already in use") } cpu.profiling = true runtime.SetCPUProfileRate(hz) go profileWriter(w) return nil }
Этот метод принимает параметр типа io.Writer
, где pprof
запишет результат анализа.
После того, как код будет проанализирован, вызовите StopCPUProfile()
, чтобы завершить анализ выполнения кода между StartCPUProfile()
и StopCPUProfile()
.
package main func main() { fmt.Println("Starting CPU-intensive task...") start := time.Now() // Create cpu profiling file, err := os.OpenFile("cpu.profiling", os.O_CREATE|os.O_RDWR, 0655) if err != nil { log.Fatal("open file error", err) } defer file.Close() pprof.StartCPUProfile(file) defer pprof.StopCPUProfile() // Perform intensive calculations calculate() elapsed := time.Since(start) fmt.Printf("Finished CPU-intensive task. Elapsed time: %s\n", elapsed) } func calculate() { for i := 0; i < 1000000000; i++ { _ = math.Sqrt(float64(i)) } }
При выполнении go run main.go
будет создан файл cpu.profiling
, в котором записано состояние выполнения программы.
Файл cpu.profiling
представляет собой двоичный файл, который невозможно понять при непосредственном открытии. Записанные в него данные о производительности не подходят для чтения человеком, поэтому нам необходимо разобрать содержимое файла с помощью команды go tool pprof
.
Мы можем выполнить команду go tool pprof cpu.profiling
для анализа сгенерированных данных о производительности.
$ go tool pprof cpu.profiling pprof go tool pprof cpu.profiling Type: cpu Time: May 6, 2023 at 10:36am (CST) Duration: 400.73ms, Total samples = 240ms (59.89%) Entering interactive mode (type "help" for commands, "o" for options) (pprof) top Showing nodes accounting for 240ms, 100% of 240ms total flat flat% sum% cum cum% 240ms 100% 100% 240ms 100% main.calculate (inline) 0 0% 100% 240ms 100% main.main 0 0% 100% 240ms 100% runtime.main (pprof)
Описание основных параметров:
flat
: Для запуска самой функции требуется время.flat%
: пропорцияflat
.cum
: общее время выполнения текущей функции и всех стеков вызовов, которые происходят внутри нее.cum%
: пропорцияcum
.sum%
: это суммаflat%
каждой строки иflat%
всех строк выше.
В приведенных выше результатах мы можем заметить, что метод main.calculate
требует наибольшего времени, что составляет 100%.
Мы также можем использовать команды top1
и topN
для просмотра самых дорогих функций 1
и N
соответственно.
Однако, поскольку у нас есть только результаты 3
, команда topN (if N > 3)
отобразит тот же результат, что и команда top3
.
Когда мы идентифицируем функцию, которая потребляет значительное количество времени, мы можем использовать команду list <function>
для просмотра стека вызовов функции и времени, затрачиваемого на каждый путь вызова.
Это предоставляет более подробную информацию о том, как вызывается функция и где тратится время внутри функции.
(pprof) list calculate Total: 240ms ROUTINE ======================== main.calculate in /pprof/main.go 240ms 240ms (flat, cum) 100% of Total . . 41: . . 42: elapsed := time.Since(start) . . 43: fmt.Printf("Finished CPU-intensive task. Elapsed time: %s\n", elapsed) . . 44:} . . 45: 240ms 240ms 46:func calculate() { . . 47: for i := 0; i < 1000000000; i++ { . . 48: _ = math.Sqrt(float64(i)) . . 49: } . . 50:} . . 51: (pprof)
Вы можете ввести команду help
, чтобы получить список доступных команд и просмотреть инструкции по их использованию. Кроме того, вы можете ввести help [command]
, чтобы получить подробную информацию о конкретной команде.
(pprof) help Commands: callgrind Outputs a graph in callgrind format comments Output all profile comments disasm Output assembly listings annotated with samples dot Outputs a graph in DOT format eog Visualize graph through eog evince Visualize graph through evince gif Outputs a graph image in GIF format gv Visualize graph through gv kcachegrind Visualize report in KCachegrind list Output annotated source for functions matching regexp ...
Профилирование памяти.
Подобно профилированию ЦП, мы можем использовать пакет pprof
для анализа использования памяти кучи нашей программой.
Для этого нам нужно вызвать функцию WriteHeapProfile()
из пакета runtime/pprof
, которая записывает данные профиля кучи в место назначения io.Writer
.
package main func main() { fmt.Println("Starting memory-intensive task...") start := time.Now() // Create memory profiling file, err := os.OpenFile("mem.profiling", os.O_CREATE|os.O_RDWR, 0655) if err != nil { log.Fatal("open file error", err) } defer file.Close() // Perform intensive calculations calculate() elapsed := time.Since(start) fmt.Printf("Finished memory-intensive task. Elapsed time: %s\n", elapsed) // Record memory usage if err := pprof.WriteHeapProfile(file); err != nil { log.Fatal(err) } } func calculate() { buf := make([]byte, 1024*1024*100) var m map[int]string = make(map[int]string) for i := 0; i < 50; i++ { m[i] = string(buf) } }
При выполнении go run main.go
будет создан файл mem.profiling
, в котором записано состояние выполнения программы.
Здесь мы можем использовать команду go tool pprof <file>
для анализа использования памяти с использованием данных профиля кучи, сгенерированных WriteHeapProfile()
.
$ go tool pprof mem.profiling Type: inuse_space Time: May 6, 2023 at 12:01pm (CST) Entering interactive mode (type "help" for commands, "o" for options) (pprof) top Showing nodes accounting for 1.86GB, 99.84% of 1.86GB total Dropped 13 nodes (cum <= 0.01GB) flat flat% sum% cum cum% 1.86GB 99.84% 99.84% 1.86GB 99.84% main.calculate (inline) 0 0% 99.84% 1.86GB 99.84% main.main 0 0% 99.84% 1.86GB 99.84% runtime.main (pprof)
Вы также можете использовать команду list <function>
в инструменте pprof
для просмотра выполнения определенной функции в программе.
(pprof) list calculate Total: 1.86GB ROUTINE ======================== main.calculate in /Users/shutiao/Desktop/Mac下软件使用/learn_go/pprof/main.go 1.86GB 1.86GB (flat, cum) 99.84% of Total . . 43: log.Fatal(err) . . 44: } . . 45:} . . 46: . . 47:func calculate() { 100MB 100MB 48: buf := make([]byte, 1024*1024*100) . . 49: var m map[int]string = make(map[int]string) . . 50: for i := 0; i < 50; i++ { 1.76GB 1.76GB 51: m[i] = string(buf) . . 52: } . . 53:} . . 54: . . 55:func mapString1() { . . 56: buf := make([]byte, 1024*1024*100) (pprof)
В нашем коде мы генерируем строку размером 100 МБ и повторяем ее 50 раз.
График пламени.
Действительно, Flame Graphs — это мощный инструмент визуализации для анализа потребления ЦП и памяти. Они обеспечивают визуальное представление трассировки стека и соответствующего использования ресурсов.
В случае с инструментом Go pprof
в более новые версии интегрирована поддержка создания Flame Graphs. Однако для создания Flame Graphs вам необходимо установить инструмент graphviz
, который является предварительным условием.
Мак:
brew install graphviz
Чтобы установить Graphviz, вы можете следовать инструкциям на официальном сайте Graphviz. На сайте представлены различные установочные пакеты для разных операционных систем.
Вы можете перейти по следующей ссылке, чтобы найти инструкции по установке и варианты загрузки для различных операционных систем: Страница загрузки Graphviz
После установки graphviz
вы можете создать Flame Graph с помощью инструмента pprof
, выполнив следующую команду:
go tool pprof -http=localhost:8080 <profile>
Замените <profile>
файлом профиля (например, профилем ЦП или профилем кучи).
Эта команда запустит локальный веб-сервер для просмотра визуализации в веб-браузере по адресу localhost:8080
.
Вы можете получить доступ к графику пламени, щелкнув опцию «График пламени» в строке меню VIEW
.
Удаленный проф.
Если вы столкнулись с проблемами в производственной среде и хотите выполнить профилирование, отправка кода непосредственно на сервер не является подходящим подходом.
Однако вы можете использовать функцию удаленного доступа инструмента pprof
для анализа производительности.
Для этого вы можете использовать пакет net/http/pprof
package для онлайн-анализа производительности.
Это позволяет удаленно получать доступ к файлам профиля и анализировать их.
Если в вашем приложении еще не запущен http-сервер, вам необходимо его запустить. Добавьте net/http
и log
к вашему импорту и следующий код к вашей основной функции:
go func() { log.Println(http.ListenAndServe("localhost:6060", nil)) }()
Далее давайте развернем код в веб-сервисе.
package main import ( "fmt" "log" "html" "net/http" _ "net/http/pprof" ) func main() { mux := http.NewServeMux() mux.HandleFunc("/cpu/", cpuCalculate) mux.HandleFunc("/memory/", memoryCalculate) // Main server server := &http.Server{ Addr: ":8088", Handler: mux, } // http pprof server go func() { log.Println(http.ListenAndServe("localhost:6060", nil)) }() if err := server.ListenAndServe(); err != nil { log.Fatal(err) } } func cpuCalculate(w http.ResponseWriter, r *http.Request) { for i := 0; i < 1000000000; i++ { _ = math.Sqrt(float64(i)) } fmt.Fprintf(w, "cpu calcaulate done, %q", html.EscapeString(r.URL.Path)) } func memoryCalculate(w http.ResponseWriter, r *http.Request) { buf := make([]byte, 1024*1024*100) var m map[int]string = make(map[int]string) for i := 0; i < 50; i++ { m[i] = string(buf) } fmt.Fprintf(w, "memory calcaulate done, %q", html.EscapeString(r.URL.Path)) }
Чтобы запустить службу и сгенерировать данные о производительности, вы можете использовать команду go run main.go
.
После запуска службы вы можете получить доступ к данным о производительности, используя следующие URL-адреса:
http://localhost:8088/cpu/
: Этот URL-адрес будет предоставлять данные профилирования ЦП.http://localhost:8088/memory/
: Этот URL-адрес будет предоставлять данные профилирования памяти.
Получив доступ к этим URL-адресам, вы можете собрать необходимые данные о производительности для анализа.
Кроме того, вы можете получить доступ к http://localhost:6060/debug/pprof/ для получения более подробной информации.
Этот URL-адрес обеспечивает доступ к различным конечным точкам pprof
, что позволяет вам собирать дополнительные данные о производительности и анализировать различные аспекты производительности вашего приложения.
$ go tool pprof -http=localhost:8081 http://localhost:6060/debug/pprof/profile Fetching profile over HTTP from http://localhost:6060/debug/pprof/profile Saved profile in /Users/pprof/pprof.samples.cpu.003.pb.gz Serving web UI on http://localhost:8081
Эта команда извлечет данные профиля ЦП с указанного URL-адреса (http://localhost:6060/debug/pprof/profile
) и запустит веб-сервер на localhost:8081
для отображения результатов анализа производительности в веб-интерфейсе.
$ go tool pprof -http=localhost:8081 http://localhost:6060/debug/pprof/heap Saved profile in /Users/pprof/pprof.alloc_objects.alloc_space.inuse_objects.inuse_space.006.pb.gz Serving web UI on http://localhost:8081
Эта команда извлечет данные профиля памяти с указанного URL-адреса (http://localhost:6060/debug/pprof/heap
) и запустит веб-сервер на localhost:8081
для отображения результатов анализа производительности в веб-интерфейсе.
Для получения дополнительной информации о том, как использовать пакет net/http/pprof
в Go, вы можете обратиться к документации по адресу https://pkg.go.dev/net/http/pprof.
Документация содержит подробные объяснения и примеры того, как использовать пакет для включения конечных точек профилирования в ваших веб-приложениях Go, анализа профилей ЦП и памяти и других задач, связанных с профилированием.
Это ценный ресурс для понимания возможностей и использования пакета net/http/pprof
для анализа производительности в Go.
В производственной среде обычно ограничивают доступ к конечным точкам профилирования из соображений безопасности.
Поэтому рекомендуется загружать файл профиля из производственной среды для выполнения локального анализа.
Как упоминалось ранее, при использовании команды go tool pprof
файл профиля сохраняется локально.
Путь и имя файла могут различаться в зависимости от используемых команды и параметров. Например, команда может сохранить файл профиля как pprof.alloc_objects.alloc_space.inuse_objects.inuse_space.006.pb.gz
в каталоге /Users/xxx/
.
Загрузив и проанализировав этот файл профиля локально, вы можете выполнить углубленный анализ производительности, не раскрывая конфиденциальную информацию и не открывая порты в производственной среде.
Если вам нравятся такие истории и вы хотите поддержать меня, пожалуйста, хлопните мне в ладоши.
Ваша поддержка очень важна для меня, спасибо.
Повышение уровня кодирования
Спасибо, что являетесь частью нашего сообщества! Перед тем, как ты уйдешь:
- 👏 Хлопайте за историю и подписывайтесь на автора 👉
- 📰 Смотрите больше контента в публикации Level Up Coding
- 💰 Бесплатный курс собеседования по программированию ⇒ Просмотреть курс
- 🔔 Подписывайтесь на нас: Twitter | ЛинкедИн | "Новостная рассылка"
🚀👉 Присоединяйтесь к коллективу талантов Level Up и найдите прекрасную работу