Профилирование программ Go с помощью Flame Graphs

задняя часть Архитектура

В разработке программного обеспечения после того, как система подключена к сети, по-прежнему необходимо постоянно оптимизировать или переконфигурировать систему.

Это базовый навык, обычно используемый в практике разработки программного обеспечения, чтобы научиться собирать данные и анализировать производительность прикладной системы во время выполнения. обычно используетсяprofileПредставляет профилирование и коллекцию или использует профилирование для представления поведения профилирования. Например, соответствующие инструменты на языке Java:jprofiler, что означает Java Profiler.

Язык Go уделяет большое внимание производительности, и его встроенная библиотека поставляется с библиотекой анализа производительности pprof. У pprof есть два пакета для анализа программ: runtime/pprof и net/http/pprof, где net/http/pprof просто инкапсулирует пакет runtime/pprof и предоставляет его через http. runtime/pprof используется для анализа производительности обычных приложений, в основном для завершаемых блоков кода, таких как вызов функции, тогда как net/http/pprof используется для сбора информации о производительности и анализа фоновых служебных программ.

В этом разделе рассказывается, как выполнять анализ и оптимизацию производительности на основе pprof, включая ЦП, использование памяти, блокировку блоков и использование горутины. Кроме того, будет представлен более интуитивно понятный графический инструмент: график пламени, который преобразует результаты pprof в график пламени на основе go-torch.

Анализ производительности общих приложений

Мы уже знаем, что runtime/pprof используется для профилирования общих приложений, в основном для конечных блоков кода. Поэтому мы будем практиковать это на примере ниже.

Вычислить пи

Случай, выбранный автором, представляет собой алгоритм вычисления числа пи.

Как мы все знаем, можно сказать, что это самая известная иррациональная константа в мире, которая представляет собой отношение длины окружности к диаметру круга или называется «пи». Около 250 г. до н.э. Архимед дал оценку «пи» между 223/71 и 22/7.

Цзу Чунчжи (429–500), известный математик времен Южной и Северной династий в Китае, впервые вычислил число «пи» с точностью до седьмого знака после запятой, то есть между 3,1415926 и 3,1415927. Только в 15 веке арабский математик Аль-Каси побил этот рекорд с «точностью до 17 знаков после запятой».
Буква, представляющая «пи», является строчной буквой шестнадцатой греческой буквы. Также первая буква греческого περιφρεια (означает периметр, площадь, окружность). В 1706 году английский математик Уильям Джонс (1675–1749) впервые использовал «π» для обозначения числа «пи». В 1736 году швейцарский математик Эйлер (Leonhard Euler, 1707-1783) также начал выражать число пи. С тех пор оно стало синонимом числа «пи».

Обычные методы расчета следующие:

  • метод Монте-Карло;
  • квадратное приближение;
  • итерационный метод;
  • формула Чудновского

Реализация тестового кода

Здесь автор использует метод Монте-Карло для вычисления числа пи.Общая идея заключается в следующем:

Внутри квадрата есть касательная окружность, и отношение их площадей равно π/4.

Внутри этого квадрата случайным образом генерируются 10 000 точек (т. е. 10 000 пар координат (x, y)) и вычисляются их расстояния от центральной точки, чтобы определить, попадают ли они внутрь круга. Если точки распределены равномерно, то точки внутри круга должны составлять π/4 всех точек, поэтому умножение этого отношения на 4 дает значение π. При случайном моделировании 30 000 точек оценочное значение π отличается от истинного значения на 0,07%.

Наконец, полный код реализации выглядит так:

package main

import (
	"flag"
	"fmt"
	"log"
	"os"
	"runtime"
	"runtime/pprof"
	"time"
)

var n int64 = 10000000000
var h float64 = 1.0 / float64(n)

func f(a float64) float64 {
	return 4.0 / (1.0 + a*a)
}

func chunk(start, end int64, c chan float64) {
	var sum float64 = 0.0
	for i := start; i < end; i++ {
		x := h * (float64(i) + 0.5)
		sum += f(x)
	}
	c <- sum * h
}

func main() {
	var cpuProfile = flag.String("cpuprofile", "", "write cpu profile to file")
	var memProfile = flag.String("memprofile", "", "write mem profile to file")
	flag.Parse()
	//采样cpu运行状态
	if *cpuProfile != "" {
		f, err := os.Create(*cpuProfile)
		if err != nil {
			log.Fatal(err)
		}
		pprof.StartCPUProfile(f)
		defer pprof.StopCPUProfile()
	}
	//记录开始时间
	start := time.Now()

	var pi float64
	np := runtime.NumCPU()
	runtime.GOMAXPROCS(np)
	c := make(chan float64, np)
	fmt.Println("np: ", np)

	for i := 0; i < np; i++ {
    //利用多处理器,并发处理
		go chunk(int64(i)*n/int64(np), (int64(i)+1)*n/int64(np), c)
	}

	for i := 0; i < np; i++ {
		tmp := <-c
		fmt.Println("c->: ", tmp)

		pi += tmp
		fmt.Println("pai: ", pi)

	}

	fmt.Println("Pi: ", pi)

	//记录结束时间
	end := time.Now()

	//输出执行时间,单位为毫秒。
	fmt.Printf("spend time: %vs\n", end.Sub(start).Seconds())
	//采样 memory 状态
	if *memProfile != "" {
		f, err := os.Create(*memProfile)
		if err != nil {
			log.Fatal(err)
		}
		pprof.WriteHeapProfile(f)
		f.Close()
	}
}

Выше приведен алгоритм вычисления π, основанный на горутине и канале языка go, в полной мере использующий многоядерные процессоры и улучшающий скорость вычисления ресурсов ЦП.

Мы представили runtime/pprof в зависимостях и добавили соответствующие коды профилирования ЦП и профилирования памяти в реализованный код для оценки производительности ЦП и памяти.

скомпилировать и выполнить

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

$ go build  -o pai main.go
$ ./pai --cpuprofile=cpu.pprof
$ ./pai --memprofile=mem.pprof

Вышеупомянутая команда генерирует два файла выборки cpu.pprof и mem.pprof по очереди, мы используемgo tool pprofкоманда для анализа:

$ go tool pprof cpu.pprof

После выполнения вышеуказанной команды вы войдете в интерактивный режим командной строки pprof. pprof поддерживает несколько инструкций.Например, top используется для отображения первых 10 элементов данных в файле pprof, а 20 строк данных могут отображаться с помощью top 20; другие инструкции, такие как list, pdf, eog и т. д.

На приведенном выше рисунке некоторые другие параметры поясняются следующим образом:

  • Продолжительность: время выполнения программы. Многоядерная программа выполнения заняла в общей сложности 13,47 с, а время выборки составило 24,44 с; каждое ядро ​​делило время выборки поровну.
  • flat/flat%: Представляет занятое время и процент загрузки ЦП на текущем уровне соответственно.
  • cum/cum%: Представляет накопленное процессорное время и пропорцию до текущего уровня соответственно.
  • sum%: Совокупное использование ЦП всех уровней, от малого до большого, было суммировано до 100%, то есть 24,44 с.

В этом примере main.chunk занимает 21,86 с процессорного времени на текущем уровне, что составляет 89,44% этого времени сбора. Совокупное время, затрачиваемое этой функцией, составляет 24,44 с, что составляет 100 % времени сбора данных. Из совокупных данных видно, что функция чанка потребляет больше всего процессорного времени.

На приведенном выше рисунке четко показаны основные функции приложения, отнимающие много времени, а затем используйте команду списка для просмотра основных факторов занятости. Команда list выводит связанные методы в соответствии с вашим регулярным выражением.Непосредственно после опции -o будут выведены все методы, и вы также можете указать имя метода. Это позволяет вам увидеть код соответствующей функции и время, затраченное на каждую строку кода:

Как видно из рисунка выше, для вызова функции f(x) в строке 24 потребовалось дополнительно 2,58 с. Отображается время, затраченное на каждую строку кода, и на основе этой информации можно проводить оптимизацию кода.

Графический рендеринг

Для результатов, собранных pprof, мы можем не только использовать команды, поставляемые с pprof для анализа, но и анализировать с помощью более интуитивно понятной векторной диаграммы. С помощью graphviz pprof может напрямую генерировать соответствующие файлы изображений.

Письменный тест основан на системе Centos 7.5.Установите graphviz напрямую с помощью следующей команды:

$ sudo yum install graphviz

Дополнительные инструкции по установке системной среды см.официальный сайт графвиза

После установки graphviz продолжайте вводить svg в интерактивной командной строке pprof:

Обратите внимание, что веб-команда не поддерживается в системах серверного типа.Используйте команду svg, чтобы создать векторную диаграмму и открыть ее в браузере, как показано ниже:

Автор перехватил часть контента.Как видно из рисунка выше, основная трудоемкая функция — это main.chunk, которая занимает 21,86 с, а ассоциированная функция f(x) — 2,58 с. Размер каждого квадрата на рисунке также представляет загрузку ЦП: чем больше квадрат, тем дольше загрузка ЦП.

Анализ производительности фоновой сервисной программы

Для запущенных фоновых служб, таких как веб-приложения или распределенные приложения, мы можем использовать библиотеку NET/HTTP/PPROF, которую можно анализировать при применении служб HTTP.

pprof собирает фоновые службы. Если используется http.DefaultServeMux по умолчанию, обычно код напрямую использует http.ListenAndServe("0.0.0.0:8000", nil). В этом случае это относительно просто, просто импортируйте пакет.

import (
	_ "net/http/pprof"
)

Обратите внимание, что пакет импортируется с символом подчеркивания "_", что означает, что нам нужен пакет только для запуска его функции init(), поэтому пакет автоматически завершит сбор информации и сохранит ее в памяти.

Если вы используете собственный мультиплексор ServerMux, вам необходимо вручную зарегистрировать некоторые правила маршрутизации:

r.HandleFunc("/debug/pprof/", pprof.Index)
r.HandleFunc("/debug/pprof/heap", pprof.Index)
r.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
r.HandleFunc("/debug/pprof/profile", pprof.Profile)
r.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
r.HandleFunc("/debug/pprof/trace", pprof.Trace)

Эти пути представляют:

  • /debug/pprof/profile: при переходе по этой ссылке автоматически выполняется профилирование ЦП на 30 секунд и создается файл для загрузки. Вы можете использовать параметр ?=seconds=60 для сбора данных за 60 секунд.
  • /debug/pprof/block: Записи событий блокировки Goroutine. По умолчанию производится выборка каждый раз, когда происходит блокирующее событие.
  • /debug/pprof/goroutines: запись информации об активных горутинах. Делайте выборку только один раз во время сбора данных.
  • /debug/pprof/heap: запись распределения памяти кучи. По умолчанию производится выборка через каждые выделенные 512 КБ.
  • /debug/pprof/mutex: просмотр владельцев конкурирующих мьютексов.
  • /debug/pprof/threadcreate: запись о создании системного потока. Делайте выборку только один раз во время сбора данных.

переписать тестовый код

Перепишите программу для вычисления pi в службу, предоставьте внешний интерфейс и внедрите зависимости net/http/pprof для сбора показателей производительности HTTP-служб.

package main

import (
	"fmt"
	"net/http"
	_ "net/http/pprof"
	"runtime"
)

var n int64 = 10000000000
var h = 1.0 / float64(n)

func f(a float64) float64 {
	return 4.0 / (1.0 + a*a)
}

func chunk(start, end int64, c chan float64) {
	var sum float64 = 0.0
	for i := start; i < end; i++ {
		x := h * (float64(i) + 0.5)
		sum += f(x)
	}
	c <- sum * h
}

func callFunc(w http.ResponseWriter, r *http.Request) {

	var pi float64
	np := runtime.NumCPU()
	runtime.GOMAXPROCS(np)
	c := make(chan float64, np)
	fmt.Println("np: ", np)

	for i := 0; i < np; i++ {
		go chunk(int64(i)*n/int64(np), (int64(i)+1)*n/int64(np), c)
	}

	for i := 0; i < np; i++ {
		tmp := <-c
		fmt.Println("c->: ", tmp)

		pi += tmp
		fmt.Println("pai: ", pi)

	}

	fmt.Println("Pi: ", pi)
}

func main() {
	http.HandleFunc("/getAPi", callFunc)
	http.ListenAndServe(":8000", nil)
}

В реализации приведенного выше кода мы открываем порт 8000 и определяем интерфейсgetAPi. Реализация вычисления pi такая же, как и раньше, и каждый вызов интерфейса запускает вычисление π один раз.

Скомпилировать и выполнить

После написания кода мы можем скомпилировать и запустить службу HTTP, а также выполнить следующую команду:

$ go build -o httpapi main.go

$ ./httpapi

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

На данный момент мы можем получить к нему доступ через HTTP-интерфейс pprof.http://локальный:8000/отладка/pprof/:

На рисунке выше показана работа сервиса веб-просмотра pprof, при этом, если веб-страница постоянно обновляется, можно обнаружить, что результаты выборки постоянно обновляются.

Графический анализ

Как и в случае с анализом производительности программы, который можно завершить выше, мы также можем проанализировать производительность фоновой программы графическим способом.

Затем используйте инструмент go tool pprof для анализа и сохранения данных.Как правило, pprof используется для доступа к перечисленным выше конечным точкам маршрутизации через HTTP для прямого получения данных и последующего их анализа.После получения данных pprof автоматически разрешает терминал введите модель взаимодействия.

Используйте следующие команды для просмотра информации, связанной с памятью:

$ go tool pprof main http://localhost:8000/debug/pprof/heap

Приведенная выше команда собирает информацию о памяти, и консоль выводит сгенерированное имя изображения: profile001.svg, которое по умолчанию находится в текущем каталоге.Конечно, мы также можем указать местоположение и имя файла.

Поскольку нет доступа к HTTP-запросу, использование памяти относительно низкое, и исключений нет. Далее мы проанализируем производительность при нормальной работе, смоделировав онлайн-ситуацию посредством стресс-тестирования.

Создавайте графики пламени с помощью go-torch

В приведенных выше подразделах представлены net/http/pprof и runtime/pprof для профилирования программ Go. Однако в приведенном выше случае отбирается только часть сегмента кода. При этом основную информацию об оптимизации службы приложений можно увидеть только при большом количестве запросов. В настоящее время нам нужно использовать go-torch, еще один инструмент Uber с открытым исходным кодом, чтобы помочь нам в завершении анализа. Для достижения эффекта графика пламени необходимо установить следующие три инструмента: компонент измерения давления wrk, график пламени FlameGraph и инструмент go-torch. Установка и использование этих трех компонентов будут представлены по очереди.

Работа компонента измерения давления

wrk — это инструмент для тестирования HTTP-протокола, который может использовать высокопроизводительный механизм ввода-вывода, поставляемый с системой, такой как epoll, kqueue и т. д., через многопоточность и режим событий в условиях одномашинной многозадачности. -ядерный процессор создает большую нагрузку. Команда установки выглядит так:

$ git clone https://github.com/brendangregg/FlameGraph.git
$ cd wrk/
$ make

С помощью приведенной выше команды мы создаем исполняемый файл wrk. Его использование относительно простое, а основные параметры описываются следующим образом:

  • -c: общее количество подключений (подключения, обрабатываемые каждым потоком = общее количество подключений/потоков)
  • -d: продолжительность теста, например, 2 с (2 секунды), 2 м (2 минуты), 2 часа (час)
  • -t: общее количество потоков для выполнения
  • -s: Выполнить Lua-скрипт, написать здесь путь и имя lua-скрипта, а дело будет дано позже
  • -H: Информация заголовка, которую необходимо добавить, обратите внимание на синтаксис заголовка, например, -H "токен: abcdef", объяснение, токен, двоеточие, пробел, abcdefg (не забудьте пробел, иначе будет сообщено об ошибке).

Параметры только что начатого автором измерения давления следующие:

./wrk -t5 -c10 -d120s http://localhost:8000/getAPi

То есть 5 потоков одновременно поддерживают 10 соединений в секунду в течение 120 с. Если возникает следующая ошибка,

unable to create thread 419: Too many open files

Это связано с тем, что количество подключений /socket превышает системные настройки, и необходимо настроить максимальное количество открытых файлов на пользователя.

$ ulimit -n 2048

График пламени FlameGraph и факел

Пламенный график — это мощный инструмент для анализа производительности, позволяющий быстро обнаружить узкие места в производительности. На серверах Linux обычно используется с perf.

go-torch — это инструмент Uber с открытым исходным кодом, который может напрямую считывать данные профилирования pprof и генерировать SVG-файл Flame Graph. Svg-файл Flame Graph можно открыть в браузере, и его преимущество перед графом вызовов заключается в том, что его содержимое можно анализировать, нажимая на каждый квадратик.

Выполните следующую команду для установки:

$ git clone https://github.com/brendangregg/FlameGraph.git
$ go get github.com/uber/go-torch

Команды, используемые go-torch, следующие:

$ go-torch -u http://localhost:8000 -t 100

Приведенная выше команда запустит пару инструментов go-torch.http://localhost:8000Соберите сотни информации.

Стресс-тест генерирует график пламени

После установки трех вышеуказанных компонентов мы проведем тестирование. Первый — запустить нашу службу приложений:

$ ./httpapi

Затем запустите испытание давлением и приступайте к работе:

$ ./wrk -t5 -c10 -d120s http://localhost:8000/getAPi
$ go-torch -u http://localhost:8000 -t 100

Видно, что запрос на наш стресс-тест уже сгенерировал соответствующий флейм-граф на сервере: torch.svg. Примечание. Запустите go-torch в каталоге FlameGraph, в противном случае путь к двоичному исполняемому файлу необходимо добавить в переменную системной среды.

Откройте график пламени следующим образом:

График пламени напоминает пламя, отсюда и название: по горизонтальной оси отложено время использования процессора, а по вертикальной оси — последовательность вызовов. Последовательность вызовов на графике пламени идет снизу вверх. Каждый квадрат представляет функцию, а слой над ним указывает, какие функции будут вызываться этой функцией. Размер квадрата представляет продолжительность использования ЦП. Соответствие цветов карты пламени не имеет особого значения, сопоставление красного и желтого цветов по умолчанию должно быть больше похоже на пламя.

Как и в случае с результатами, которые мы проанализировали выше, общее время тратится на функцию чанка. Давайте посмотрим на другой график пламени, когда доступ не запрашивается:

Видно, что в этом случае время использования ЦП и использование памяти очень стабильны, в основном это касается библиотечных функций, предоставляющих услуги http.

резюме

В этой статье в основном рассказывается, как собирать показатели производительности и анализировать производительность приложений Go с помощью pprof. Мы получаем подробную информацию об использовании ЦП и памяти через pprof, что может дополнительно указать, какие функции занимают много времени, и цепочку вызовов между функциями. Если вам нужен более подробный анализ, вы должны быть точны на уровне кода, смотреть на время, затрачиваемое на каждую строку кода, и непосредственно находить строку кода, в которой есть проблемы с производительностью.

В сочетании с go-torch от Uber с открытым исходным кодом для создания графиков пламени мы можем просматривать память и ЦП, а также горутины, блокирующие блокировки и т. д. системы во время выполнения с глобальной точки зрения.Умение использовать инструменты анализа производительности может помочь мы быстрее находим и решаем онлайн-проблемы проблема ошибка.

Из объяснения этой статьи вы также узнали, что для анализа производительности фоновой программы требуется запрос, а не статическая служба.В этой статье используется стресс-тест для имитации большого количества запросов. Конечно, при включении pprof в производственной среде также необходимо учитывать накладные расходы на производительность.Это определенно лучший выбор для решения проблемы перед выходом в онлайн.

Читайте последние статьи Подпишитесь на официальный аккаунт: aoho seeks