Инструмент мониторинга/профилирования производительности Go: go tool pprof

Go

мы можем использоватьgo tool pprofкоманда для интерактивного доступа к содержимому профиля. Команда проанализирует указанный профиль и предоставит нам хорошо читаемую выходную информацию в соответствии с нашими требованиями.

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

Профиль процессора

Прежде чем представить метод генерации профиля ЦП, давайте кратко рассмотрим частоту ЦП. Основная частота ЦП, то есть тактовая частота, на которой работает ядро ​​ЦП (CPU Clock Speed). Основной единицей основной частоты ЦП является герц (Гц), но чаще всего она измеряется в мегагерцах (МГц) или гигагерцах (ГГц). Инверсия тактовой частоты - это тактовый период. Базовой единицей тактового периода является секунда (с), но чаще всего это миллисекунды (мс), микросекунды (нас) или наносекунды (нс). За один такт ЦП выполняет операционную инструкцию. То есть при частоте ЦП 1000 Гц одна рабочая инструкция ЦП может выполняться каждую 1 миллисекунду. При частоте ЦП 1 МГц одна рабочая инструкция ЦП может выполняться каждую 1 микросекунду. пока в 1 При основной частоте ЦП в ГГц одна рабочая инструкция ЦП может выполняться каждую 1 наносекунду.

По умолчанию система выполнения Go использует ЦП с частотой 100 Гц. То есть 100 выборок в секунду, то есть одна выборка каждые 10 миллисекунд. Зачем использовать эту частоту? Потому что 100 Гц достаточно для генерации полезных данных без остановки системы. И число 100 также легко преобразовать, например, преобразовать общее количество выборок в количество выборок в секунду. На самом деле упомянутая здесь выборка использования ЦП — это выборка счетчика программ в стеке текущей горутины. Исходя из этого, мы можем проанализировать из выборочных записей, какой код является наиболее трудоемким или ресурсоемким. Мы можем начать запись использования ЦП со следующего кода.

func startCPUProfile() {
	if *cpuProfile != "" {
		f, err := os.Create(*cpuProfile)
		if err != nil {
			fmt.Fprintf(os.Stderr, "Can not create cpu profile output file: %s",
				err)
			return
		}
		if err := pprof.StartCPUProfile(f); err != nil {
			fmt.Fprintf(os.Stderr, "Can not start cpu profile: %s", err)
			f.Close()
			return
		}
	}
}
 

в функцииstartCPUProfile, мы сначала создали файл для хранения записей об использовании ЦП. Этот файл является профилем ЦП, и его абсолютный путь задается как*cpuProfileзначение представляет. Затем мы передаем экземпляр этого файла в качестве параметра функцииpprof.StartCPUProfile中。如果此函数没有返回错误,就说明记录操作已经开始。需要注意的是,只有CPU概要文件的绝对路径有效时此函数才会开启记录操作。

如果我们想要在某一时刻停止CPU使用情况记录操作,就需要调用下面这个函数:

func stopCPUProfile() {
	if *cpuProfile != "" {
		pprof.StopCPUProfile() // 把记录的概要信息写到已指定的文件
	}
}
 

В этой функции нет кода для операции записи профиля ЦП. Фактически система выполнения записывает выборочные данные в профиль ЦП 100 раз в секунду после начала операции записи использования ЦП.pprof.StopCPUProfileФункция останавливает операцию выборки, устанавливая частоту выборки использования ЦП на 0. И только после того, как все записи об использовании ЦП будут записаны в профиль ЦП,pprof.StopCPUProfileфункция выйдет. Это обеспечивает целостность профиля ЦП.

профиль памяти

Профили памяти используются для сохранения использования памяти во время выполнения пользовательской программы. Упомянутое здесь использование памяти на самом деле является выделением памяти кучи во время выполнения программы. Система времени выполнения языка Go записывает все выделения памяти в куче во время выполнения пользовательской программы. Независимо от того, увеличилось ли количество используемых байтов памяти кучи во время выборки, анализатор будет производить выборку байтов до тех пор, пока выделено достаточное количество байтов. Вот как включить ведение журнала использования памяти:

func startMemProfile() {
	if *memProfile != "" && *memProfileRate > 0 {
		runtime.MemProfileRate = *memProfileRate
	}
}
 

Как мы видим, способ включения логирования использования памяти очень прост. в функцииstartMemProfile, только в*memProfileа также*memProfileRateПоследующие операции будут выполняться только тогда, когда значение допустимо.*memProfileСмысл - абсолютный путь к профилю памяти.*memProfileRateИмеется в виду интервал выборки анализатора, в байтах. Когда мы присваиваем это значение переменной типа intruntime.MemProfileRate, это означает, что профилировщик будет производить выборку использования памяти после каждого выделения указанного количества байтов. На самом деле, даже если мы не даемruntime.MemProfileRateТакже будет выполняться назначение переменных, операция выборки использования памяти. Эта операция выборки начинается в начале программы пользователя и продолжается до конца программы пользователя.runtime.MemProfileRateЗначение переменной по умолчанию равно512 * 1024, что составляет 512 Кбайт. Только когда мы явно устанавливаем0назначатьruntime.MemProfileRateПосле переменной операция выборки отменяется.

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

func stopMemProfile() {
	if *memProfile != "" {
		f, err := os.Create(*memProfile)
		if err != nil {
			fmt.Fprintf(os.Stderr, "Can not create mem profile output file: %s", err)
			return
		}
		if err = pprof.WriteHeapProfile(f); err != nil {
			fmt.Fprintf(os.Stderr, "Can not write %s: %s", *memProfile, err)
		}
		f.Close()
	}
}
 

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

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

Профиль блокировки программы

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

func startBlockProfile() {
	if *blockProfile != "" && *blockProfileRate > 0 {
		runtime.SetBlockProfileRate(*blockProfileRate)
	}
}
 

Аналогично включению ведения журнала использования памяти в функцииstartBlockProfileв, когда*blockProfileа также*blockProfileRateКогда значение допустимо, мы устанавливаем интервал выборки для событий блокировки Goroutine.*blockProfileозначает абсолютный путь к профилю блокировки программы.*blockProfileRateИмеется в виду интервал выборки анализатора, единица измерения – раз. функцияruntime.SetBlockProfileRateЕдинственный параметр имеет тип int. Это означает, что профилировщик будет отбирать события блокировки Goroutine каждые несколько раз, когда они происходят. Если мы не используем явноruntime.SetBlockProfileRateФункция устанавливает интервал выборки, тогда интервал выборки равен 1. То есть по умолчанию профилировщик будет производить выборку каждый раз, когда происходит событие блокировки Goroutine. Подобно записям об использовании памяти, система выполнения отбирает события блокировки Goroutine во время выполнения пользовательской программы. Однако если мы пройдемruntime.SetBlockProfileRateФункция устанавливает этот интервал выборки равным0или отрицательное, то операция выборки будет отменена.

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

func stopBlockProfile() {
	if *blockProfile != "" && *blockProfileRate >= 0 {
		f, err := os.Create(*blockProfile)
		if err != nil {
			fmt.Fprintf(os.Stderr, "Can not create block profile output file: %s", err)
			return
		}
		if err = pprof.Lookup("block").WriteTo(f, 0); err != nil {
			fmt.Fprintf(os.Stderr, "Can not write %s: %s", *blockProfile, err)
		}
		f.Close()
	}
}
 

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

больше профилей

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

Таблица 0-20 Записи, которые можно получить с помощью функции pprof.Lookup

имя иллюстрировать Частота дискретизации
goroutine Запись информации об активных горутинах. Делайте выборку только один раз во время сбора данных.
threadcreate Запись о создании системного потока. Делайте выборку только один раз во время сбора данных.
heap Запись о выделении памяти в куче. По умолчанию производится выборка через каждые выделенные 512 КБ.
block Запись событий блокировки Goroutine. По умолчанию производится выборка каждый раз, когда происходит блокирующее событие.

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

Мы знаем, что во время работы пользовательской программы различные состояния постоянно меняются. Особенно для последних двух записей, с увеличением времени выборки количество элементов записи будет продолжать расти. Для первых двух записей «goroutine» и «threadcreate», если создается новая активная goroutine или создается новый системный поток, количество записей также будет увеличиваться. Поэтому исполняющая система языка Go сначала оценит количество элементов записи при выборке записей из памяти. Если между предполагаемым количеством записей и получением записей создается новая запись, система выполнения попытается повторно получить все записи. Кроме того, система выполнения использует срезы для загрузки всех записей. Если текущий используемый срез не содержит всех записей, система среды выполнения создаст больший срез на основе текущего общего количества записей и попытается снова загрузить все записи. Пока этот фрагмент не станет достаточно большим, чтобы вместить все записи. Однако, если записи растут слишком быстро, системе выполнения придется продолжать попытки. Это может привести к чрезмерным затратам времени. Для первых двух записей «goroutine» и «threadcreate» размер среза, созданного системой выполнения, равен общему количеству элементов текущей записи плюс 10. Для первых двух записей «куча» и «блок» размер среза, созданного системой выполнения, равен общему количеству элементов текущей записи плюс 50. Хотя вероятность описанной выше ситуации может быть не слишком высока, если для получения вышеуказанных записей для некоторых пользовательских программ с высокой степенью параллелизма требуется слишком много времени, мы можем сначала проверить такие причины. На самом деле упомянутое выше Этот тип операции записи больше подходит для определения состояния пользовательских программ с высокой степенью параллелизма.

Мы можем вывести четыре типа записей в файл с помощью следующей функции.

func SaveProfile(workDir string, profileName string, ptype ProfileType, debug int) {
	absWorkDir := getAbsFilePath(workDir)
	if profileName == "" {
		profileName = string(ptype)
	}
	profilePath := filepath.Join(absWorkDir, profileName)
	f, err := os.Create(profilePath)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Can not create profile output file: %s", err)
		return
	}
	if err = pprof.Lookup(string(ptype)).WriteTo(f, debug); err != nil {
		fmt.Fprintf(os.Stderr, "Can not write %s: %s", profilePath, err)
	}
	f.Close()
}
 

функцияSaveProfileЕсть четыре параметра. Первый параметр — это каталог, в котором хранится профиль. Второй параметр — это имя профиля. Третий параметр — тип профиля. Среди них типProfileTypeПросто псевдоним для строкового типа. Это необходимо для ограничения его стоимости. Его значение должно быть одним из «goroutine», «threadcreate», «heap» или «block». Теперь сосредоточимся на четвертом параметре. параметрdebugУправляет уровнем детализации информации в профиле. Этот параметр также передается в структуруpprof.Profileметод указателяWriteToвторой параметр . а такжеpprof.ProfileУказатель на экземпляр структуры используется функциейpprof.Lookupпроизводить. Посмотрим, как значение параметра debug соотносится с содержимым записи, записываемой в профиль.

Таблица 0-21 Влияние отладки параметра на содержимое профиля

журнал\отладка меньше или равно 0 равно 1 больше или равно 2
goroutine Для каждой записи укажите адрес памяти в шестнадцатеричном формате записи в стеке вызовов. На основе информации, предоставленной слева, укажите имя функции с путем импорта пакета кода, соответствующим адресу памяти, пути к файлу исходного кода и номеру строки, где находится исходный код для каждого элемента в стеке вызовов каждый элемент записи. Предоставляет информацию о состоянии и информацию о стеке вызовов каждой активной горутины в легко читаемом виде.
threadcreate То же. То же. То же, что слева.
heap То же. На основе информации, предоставленной слева, предоставьте имя функции с путем импорта пакета кода, соответствующим адресу памяти, пути к файлу исходного кода и строке, где находится исходный код для каждого элемента в стеке вызовов каждого запись элемента и предоставление информации о состоянии памяти. То же, что слева.
block То же. На основе информации, предоставленной слева, укажите имя функции с путем импорта пакета кода, соответствующим адресу памяти, пути к файлу исходного кода и номеру строки, где находится исходный код для каждого элемента в стеке вызовов каждый элемент записи. То же, что слева.

Из вышеприведенной таблицы видно, что приdebugменьше или равно0, система выполнения записывает в профиль только базовую информацию из каждой записи. Основная информация о записи состоит только из адреса памяти в шестнадцатеричном формате записи в стеке вызовов.debugЧем больше значение , тем больше информации мы можем получить из профиля. но,go tool pprofКоманды игнорируют дополнительную информацию помимо основной информации. Фактически, система выполнения записывает дополнительную информацию в профиль со знаком «#» в крайнем левом углу, чтобы обозначить текущую строку комментария к поведению. Из-за этого префиксаgo tool pprofкоманда пропустит синтаксический анализ этой дополнительной информации. Одно исключение из этого, когдаdebugбольше или равно2В то время запись горутины не добавляет информацию на основе базовой информации, а полностью записывает информацию о состоянии и информацию о стеке вызовов каждой активной горутины в легко читаемом виде. Кроме того, в крайнем левом углу всех строк нет префикса «#». Очевидно, что этот профиль не может бытьgo tool pprofразбор команды. Но это будет более интуитивно понятно и полезно для нас.

До сих пор мы ввели использование пакетов кода стандартной библиотеки.runtimeа такжеruntime/pprofВсе методы создания профилей для программ в формате . Весь код в приведенном выше примере хранится в пакете кода проекта goc2p.basic/profсередина. пакет кодаbasic/profЭти программы очень просты в использовании. Однако, поскольку язык Go в настоящее время не имеет API (интерфейс прикладной программы), аналогичного хуку выключения (Shutdown Hook), пакет кодаbasic/profПрограммы в настоящее время могут использоваться только интрузивным способом.

инструмент pprof

Мы упоминали в предыдущем подразделе, что любойgo toolСпециальные инструменты, указанные в открывающих командах Go, хранятся в каталоге $GOROOT/pkg/tool/$GOOS_$GOARCH/. Мы называем этот каталог каталогом инструментов Go. В отличие от других специальных инструментов, инструмент pprof написан не на Go, а на Perl. (Perl — динамический интерпретируемый язык программирования общего назначения.) В отличие от Go, Perl может напрямую читать исходный код и запускать его. Следовательно,pprofФайлы исходного кода для инструментов хранятся непосредственно в каталоге инструментов Go. Для других инструментов Go все исполняемые файлы, существующие в этом каталоге, компилируются и генерируются. Мы можем напрямую открыть исходный файл pprof инструмента pprof в каталоге инструментов Go с помощью любого инструмента для просмотра текста. На самом деле этот файл с исходным кодом скопирован из открытого проекта gperftools, инициированного Google. В этот проект включено множество полезных инструментов. Эти инструменты могут помочь разработчикам создавать более надежные приложения. pprof — один из таких очень полезных инструментов.

потому чтоpprofИнструмент написан на языке Perl, поэтому выполнитеgo tool pprofПредпосылкой для команды является то, что язык Perl должен быть установлен в текущей среде.Рекомендуемый номер версии 5.x. Метод установки языка Perl здесь не описывается, читатель может найти этот метод и установить его самостоятельно. После установки языка Perl мы можем войти в каталог инструментов Go в терминале командной строки и выполнить командуperl pprof. Он работает с нами для выполнения в любом каталогеgo tool pprofЭффект команды одинаково. Конечно, если вы хотитеgo tool pprofКоманды могут выполняться в любом каталоге.Сначала нам нужно установить переменные среды, связанные с языком Go.

Мы обсудили в этом подразделе,go tool pprofКоманда анализирует указанный профиль и позволяет нам в интерактивном режиме получить доступ к информации в нем. Но одного профиля недостаточно, нам нужен еще и источник информации в профиле - исполняемый файл исходного кода команды. В конце концов, информация в профиле является результатом выборки пользовательской программы во время выполнения. Исполняемая программа на языке Go, которую можно запустить, может быть только исполняемым файлом, сгенерированным после компиляции файла исходного кода команды. Поэтому для демонстрацииgo tool pprofИспользование команды, мы также создаем или преобразуем исходный файл команды. В пакете кода нашего проекта goc2p есть исходный файл команды с именем showpds.go. Этот исходный файл команды используется для анализа зависимостей указанного пакета кода и печати этих зависимостей на стандартном устройстве вывода. Причина выбора этого исходного файла команд заключается в том, что мы можем контролировать время выполнения этого исходного файла команд, изменяя указанный пакет кода. Различные пакеты кода могут иметь разное количество прямых и косвенных зависимостей. Пакет кода с большим количеством зависимостей приведет к тому, что исходному файлу команды потребуется больше времени для разрешения его зависимостей. Чем дольше работает исходный файл команды, тем более значимую информацию в профиле мы получаем. Чтобы сгенерировать профиль, нам нужно немного изменить исходный файл команды. Сначала нам нужно импортировать пакет кода в этот файлbasic/prof. Затем нам нужно добавить строку кода в начало его основной функции.prof.Start(). Эта строка кода предназначена для проверки соответствующего флага и включения или установки соответствующей операции записи использования, когда флаг действителен. Наконец, нам также нужно добавить строку кода в блок отложенной функции main.prof.Stop(). Смысл этой строки кода в том, чтобы получить выборочные данные открытых записей и записать их в указанный профиль. С помощью этих трех шагов мы прикрепили возможность создания профиля среды выполнения к исходному файлу команды. Чтобы включить эти функции, мне также нужно выполнитьgo runКогда команда запускает исходный файл команды, добавьте соответствующую отметку. пакет кодаbasic/profДопустимые теги для программ перечислены в следующей таблице.

Таблица 0-22 Допустимые теги для API пакета кода basic/prof

название тэга описание тега
-cpuprofile Указывает путь сохранения профиля ЦП. Путь может быть относительным или абсолютным, но его родитель должен уже существовать.
-blockprofile Указывает путь для сохранения профиля блокировки программы. Путь может быть относительным или абсолютным, но его родитель должен уже существовать.
-blockprofilerate Определите его значение как n. Этот флаг указывает, что операция выборки выполняется каждые n раз блокирующих событий Goroutine.
-memprofile Указывает путь для сохранения профиля памяти. Путь может быть относительным или абсолютным, но его родитель должен уже существовать.
-memprofilerate Определите его значение как n. Этот флаг указывает, что операция выборки должна выполняться каждый раз, когда выделяется n байт памяти кучи.

Ниже мы используемgo runКоманда запускает измененный исходный файл команды showpds.go. Пример выглядит следующим образом:

hc@ubt:~/golang/goc2p$ mkdir pprof
hc@ubt:~/golang/goc2p$ cd helper/pds
hc@ubt:~/golang/goc2p/helper/pds$ go run showpds.go -p="runtime" cpuprofile="../../../pprof/cpu.out" -blockprofile="../../../pprof/block.out" -blockprofilerate=1 -memprofile="../../../pprof/mem.out" -memprofilerate=10
The package node of \'runtime\': {/usr/local/go/src/pkg/ runtime [] [] false}
The dependency structure of package \'runtime\':
runtime->unsafe
 

В приведенном выше примере мы использовали все пары пакетов кода.basic/profДопустимая разметка API. Кроме того, отметьте-pОн действителен для исходного файла команды showpds.go. Его смысл заключается в указании пути импорта пакета кода, зависимости которого должны быть разрешены.

Теперь давайте посмотрим на подкаталог pprof в каталоге проекта goc2p:

hc@ubt:~/golang/goc2p/helper/pds$ ls ../../../pprof
block.out  cpu.out  mem.out
 

Три файла в этом каталоге соответствуют трем профилям, содержащим данные в реальном времени. Это также доказывает, что наша модификация исходного файла команды showpds.go эффективна.

Что ж, все готово к работе. Теперь давайте посмотримgo tool pprofкоманда может делать что угодно. Во-первых, давайте скомпилируем исходный файл команды showpds.go.

hc@ubt:~/golang/goc2p/helper/pds$ go build showpds.go
hc@ubt:~/golang/goc2p/helper/pds$ ls
showpds showpds.go
 

Затем нам нужно подготовить профиль. Стандартный пакет кода библиотекиruntimeСуществует очень мало зависимостей, что делает исполняемые ShowPDS, выполняемые до завершения в течение очень короткого времени. Мы сказали до этого, чем дольше проходит программа, тем лучше. Поэтому нам нужно найти код кода с большим количеством прямых и косвенных зависимостей. Коллеги, которые сделали разработку системы веб-приложений, знают, что внутренняя программа системы веб-приложений может иметь много зависимостей, будь то кодовая база или другие ресурсы. Согласно нашей интуиции, это должно быть случай в мире ухода. В стандартной библиотеке языка Go, кодовой пакетnet/httpОн специально используется для предоставления различной поддержки API для разработки системы веб-приложений. Мы используем этот пакет кода для создания необходимого профиля.

hc@ubt:~/golang/goc2p/helper/pds$ ./showpds -p="net/http" -cpuprofile="../../../pprof/cpu.out" -blockprofile="../../../pprof/block.out" -blockprofilerate=1 -memprofile="../../../pprof/mem.out" -memprofilerate=10
 

Стандартный пакет кода библиотекиnet/httpЕсть много зависимостей. Из-за этого я игнорирую весь вывод. Читатели могут попробовать приведенную выше команду самостоятельно. Мы сгенерировали все профили, которые смогли создать за один раз, в качестве резервной копии. Этот профиль сохраняется в каталоге pprof проекта goc2p. Если перед выполнением вышеуказанной команды каталог pprof отсутствует, команда сообщит об ошибке. Таким образом, читатели должны сначала создать этот каталог.

Теперь выполняем его с исполняемым файлом showpds и профилем процессора cpu.out в каталоге pprof в качестве параметраgo tool pprofЗаказ. На самом деле мы проходимgo tool pprofКоманда входит в интерфейс интерактивного режима инструмента pprof.

hc@ubt:~/golang/goc2p/helper/pds$ go tool pprof showpds ../../../pprof/cpu.out
Welcome to pprof!  For help, type \'help\'.
(pprof)
 

Мы можем просмотреть профиль, введя некоторые команды после приглашения «(pprof)». Команды, поддерживаемые инструментом pprof в интерактивном режиме, следующие.

Табл. 0-23 Команды, поддерживаемые инструментом pprof в интерактивном режиме

имя параметр Этикетка иллюстрировать
gv [focus]   Отображает текущий профиль графически и иерархически. При отсутствии параметров отображаются все образцы в профиле. Если указан параметр focus, в стеке вызовов отображается только выборка функций или методов с именами, соответствующими этому параметру. Параметр фокуса должен быть регулярным выражением.
web [focus]   Подобно команде gv, веб-команда также отображает профили в графическом виде. Но разница в том, что веб-команда отображает его в веб-браузере. Если ваш веб-браузер уже запущен, он отобразится очень быстро. Если вы хотите изменить используемый веб-браузер, вы можете установить символическую ссылку /etc/alternatives/gnome-www-browser или /etc/alternatives/x-www-browser в Linux или изменить связанный Finder для файлов SVG в ОС. Х .
list [routine_regexp]   Перечислите соответствующий исходный код функции или метода, имя которого соответствует регулярному выражению, представленному параметром «routine_regexp».
weblist [routine_regexp]   Отображает то же содержимое, что и вывод команды list в веб-браузере. По сравнению с командой list у нее есть то преимущество, что когда мы нажимаем на строку исходного кода, также может отображаться соответствующий ассемблерный код.
top[N]   [--cum] Команда top может отображать функции или методы и связанную с ними информацию в порядке подсчета локальных выборок. Если присутствует флаг "--cum", то порядок подсчета кумулятивной выборки. По умолчанию команда top перечисляет первые 10 элементов. Но если за командой top следует число, в нем будет указано такое же количество элементов, как и это число.
disasm [routine_regexp]   Отображает дизассемблирование функции или метода, имя которого соответствует параметру «routine_regexp». Кроме того, соответствующий счетчик выборки будет отмечен в отображаемом контенте.
callgrind [filename]   Используйте инструмент callgrind для создания файлов статистики. В этом файле описывается вызов функций в программе. Если параметр «имя файла» не указан, инструмент kcachegrind вызывается напрямую. kcachegrind может визуализировать файлы статистики, сгенерированные инструментом callgrind.
help     Показать справочную информацию.
quit     Выйдите из команды go tool pprof. Ctrl-d также может добиться того же эффекта.

Большинство команд в приведенной выше таблице (кроме help и quit) могут добавлять один или несколько аргументов ко всем своим аргументам и меткам, чтобы указать имя функции или метода, которые вы хотите игнорировать при отображении. Нам нужно префикс таких параметров со знаком минус "-", а несколько параметров должны быть разделены пробелами. Конечно, мы также можем заменить имя функции или метода регулярным выражением. После добавления этих ограничений любые образцы в стеке вызовов, содержащие функции или методы с именами, соответствующими таким параметрам, не будут отображаться в выводе команды. Ниже мы опишем эти команды одну за другой.

команда gv

Для использования команды gv см. следующий пример:

hc@ubt:~/golang/goc2p/helper/pds$ go tool pprof showpds ../../../pprof/cpu.out
Welcome to pprof!  For help, type \'help\'.
(pprof) gv
Total: 101 samples
sh: 1: dot: not found
go tool pprof: signal: broken pipe
 

Где "(pprof)" - подсказка инструмента pprof в интерактивном режиме.

Из вывода мы видим, что команда gv была выполнена неправильно. Причина в том, что командная точка не найдена. После расследования эта команда принадлежит программному обеспечению с открытым исходным кодом Graphviz. Основная функция Graphviz — визуализация графиков. Мы можем использовать командуsudo apt-get install graphvizдля установки этого программного обеспечения. Обратите внимание, что приведенная выше команда доступна только для дистрибутива Debian Linux и его производных. Если вы используете дистрибутив Redhat Linux и его производные, вы можете использовать команду «yum install graphviz» для установки Graphviz. После установки Graphviz снова выполним команду gv.

(pprof) gv
Total: 101 samples
gv -scale 0
(pprof) sh: 1: gv: not found
 

Теперь вывод говорит нам, что команда gv не найдена. gv - это программное обеспечение с открытым исходным кодом в проекте разработки свободного программного обеспечения GNU (GNU's Not Unix), которое используется для просмотра PDF-документов в графическом виде. Устанавливаем так же. В дистрибутиве Linux Debian и его производных выполните командуsudo apt-get install gv, в дистрибутиве Redhat Linux и его производных выполните командуyum install gv. После установки программного обеспечения gv снова выполняем команду gv. В операционной системе Linux, работающей с программным обеспечением с графическим интерфейсом, появится такое окно. Рисунок 5-3.

pprof工具的gv命令的执行结果

Рисунок 0-3 Результат выполнения команды gv утилиты pprof

Мы видим, что в верхней части сводного графика отображается некоторая основная информация. где «showpds» — это исполняемый файл, который мы использовали для создания профиля. Это также источник контента в профиле. Число 23 после «Всего выборок:» означает, что анализатор произвел в общей сложности 23 выборки во время выполнения этой программы. Мы уже знаем, что выборка использования ЦП происходит каждые 10 миллисекунд. Таким образом, выборка 23 раза означает, что процессорное время, затрачиваемое на выполнение программы, приблизительно равно10毫秒 * 23 = 0.23秒. Поскольку мы не добавили параметр focus для ограничения отображаемого содержимого после команды gv, в поле «Фокусировка Число после «on:» также равно 23. Именно по этой причине числа в следующих двух строках информации равны 0. Читатели могут проверить случай добавления параметра фокуса после команды gv, например:gv ShowDepStruct. В следующем описании функции и методы мы будем называть функциями.

Теперь нанесем на график основную линию взгляда. Этот шаблон имеет прямоугольную форму и состоит из сегмента линии. В большинстве прямоугольников этой графики включены три строки информации. Первая строка — это имя функции. Вторая строка содержит количество выборочной функции местного (число в скобках слева) и долю (процент в скобках) занятых в общем количестве выборок. Третья строка содержит кумулятивный подсчет функции выборки (число в скобках слева) и отношение (процент в скобках), занятое в общем количестве выборок.

Во-первых, читатели должны разобраться с двумя похожими, но разными понятиями, а именно: локальные выборочные подсчеты и кумулятивные выборочные подсчеты. Смысл локального счетчика выборки — это количество раз, когда текущая функция непосредственно появляется в выборке. Значение кумулятивного счетчика выборки — это количество раз, которое текущая функция и текущая функция прямо или косвенно вызывает при выборке. Поэтому есть такой сценарий: для функции ее локальный счетчик выборок равен 0. Потому что он не появился непосредственно в выборке. Однако, поскольку это прямо или косвенно называется функциональностью непосредственно в выборке, совокупный счетчик выборки этой функции будет высоким. Функция mi.main в нашем примере приведена в качестве примера. Поскольку функция main.main не появлялась непосредственно во всех выборках, ее локальный счетчик выборок был равен 0. Но поскольку это функция входа в исходный файл команды, другие функции в программе вызываются прямо или косвенно. Поэтому его кумулятивное количество выборок является самым высоким среди всех функций, достигая 22. Обратите внимание, что независимо от того, является ли локальный счетчик выборки или кумулятивный счетчик выборки, функция не вычисляет свой собственный вызов. Функция вызывает рекурсивные вызовы собственных вызовов.

Наконец, следует отметить, что направленный отрезок на графике представляет вызывающую связь между функциями. Число рядом с направленным сегментом — это количество вызовов функции в начале сегмента к функции в конце сегмента. Упомянутое здесь количество вызовов фактически основано на совокупном количестве выборок функции. Более конкретно, если существует направленный отрезок от функции A к функции B и число рядом с ним равно 10, то это означает, что 10 отсчетов в совокупном отсчете выборки функции B вызваны прямыми вызовами функции A к функции B. , вызванный. Также по этой причине количество вызовов функции A к функции B должно быть меньше или равно совокупному количеству выборок функции B.

До сих пор мы объясняли все элементы схемы, и я полагаю, что читатель смог ее понять. Итак, как мы можем проанализировать программу с помощью схемы схемы?

Мы можем думать об основном графике в сводном графике как о графике вызова функции. Как правило, локальное количество выборок функции в нетерминальном узле будет очень маленьким, по крайней мере, намного меньше, чем совокупное количество выборок функции. Потому что все они достигают своих собственных функций, вызывая другие функции. Кроме того, все функции кода, написанного на языке Go, в конечном итоге должны полагаться на API, предоставляемый операционной системой. Функции в позиции конечного узла обычно существуют в файлах исходного кода, связанных с платформой, и даже некоторые функции сами по себе являются отображением API операционной системы на языке Go. Их кумулятивный подсчет выборки такой же, как подсчет локальной выборки. Поэтому описание для этого типа функции состоит всего из двух строк, ее имени и ее совокупного количества выборок.

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

Мы можем рассчитать время, необходимое для выполнения функции, по ее совокупному количеству выборок. Чем выше совокупное количество выборок функции, тем больше процессорного времени требуется для ее вызова. В частности, мы можем умножить интервал выборки ЦП (10 мс) на совокупное количество выборок функции, чтобы получить фактическое время, которое потребовалось. Хотя это фактическое время с точностью до 10 миллисекунд, этого достаточно для анализа производительности программы. Даже если совокупное количество выборок функции велико, мы не можем решить, что сама функция проблематична. Мы должны следить за потоком и находить функции, которые являются наиболее ресурсоемкими среди функций, вызываемых прямо или косвенно этой функцией. На самом деле такой поиск прост, потому что у нас уже есть схематическая диаграмма. На графике вызова функции функция с большим совокупным количеством выборок имеет большую площадь узла (прямоугольник на графике). Однако есть исключения, то есть входная функция программы. Вообще говоря, во всей связи вызова функции существует не более одной функции в том же направлении рядом с начальной позицией и соединенной с направленным отрезком линии, которую можно назвать функцией входа. Независимо от их совокупного количества выборок, площадь узла, которому они принадлежат, является наименьшей в графе вызовов функций. Поскольку почти все функции, появляющиеся в графе выборок и вызовов функций, происходят от прямых или косвенных вызовов входных функций, кумулятивное число выборок входных функций должно быть наибольшим среди них. В общем, нам не нужно заботиться о значении счетчика функции входа, поэтому нет необходимости использовать большую площадь узлов в графе вызовов функций, чтобы выделить их. На рисунке 5-3 функцияruntime.mainа такжеmain.mainможно рассматривать как входные функции. Кроме того, на графике отношения вызовов функций толщина направленного сегмента линии также отражает размер соответствующего счетчика вызовов.

Ниже авторы резюмируют три процедуры анализа функции с точки зрения связанных с ней счетчиков:

  1. Если кумулятивное количество выборок и процент функции в позиции конечного узла высоки, она сама по себе тратит слишком много процессорного времени. В это время необходимо проверить, действительно ли работа, реализованная этой функцией, занимает столько времени. Если потраченное время превышает нашу оценку, нам нужно найти наиболее трудоемкий код в теле функции через команду list и провести дальнейший анализ. Если мы обнаружим, что у этой функции слишком много обязанностей, мы можем напрямую разделить функцию на несколько более мелких функций с разными обязанностями.

  2. Если кумулятивное количество выборок и процент функции в нетерминальной позиции узла велики и превышают наши оценки, то мы должны сначала посмотреть на размер ее локального количества выборок. Если его локальное число выборок и процентное соотношение также велики, нам необходимо дополнительно проанализировать код в теле этой функции с помощью команды списка. В противном случае мы должны сосредоточиться на функции, соответствующей узлу ветви под ним. Если совокупная выборка функций всех узлов прямого ветвления под текущим узлом невелика, но число узлов прямого ветвления очень велико (десяток или даже десятки), то можно грубо сделать вывод, что функции текущий узел взял на себя слишком много обязанностей, связанных с контролем процесса, нам нужно разделить или даже перепроектировать его. Если узел ветвления под текущим узлом содержит функцию с большим кумулятивным количеством выборок и процентом, то мы должны анализировать ее в процессе 1 или процессе 2 в зависимости от типа этого узла ветвления (терминальный узел или нетерминальный узел).

  3. Только с точки зрения подсчета вызовов мы не можем судить, берет ли функция на себя слишком много обязанностей или содержит слишком много логики управления потоком. Однако мы можем использовать подсчет вызовов в качестве помощи в обнаружении проблем. Например, если на основе анализа в Процессе 1 и Процессе 2 мы подозреваем, что в функцииBВ вызываемой функции могут быть проблемы с производительностью, и мы также обнаружили, что функцияAпарная функцияBКоличество вызовов также очень велико, тогда мы должны подумать о функцииBЧастые появления в выборках могут быть вызваны функциейAпарная функцияBиз-за частых звонков. В этом случае сначала следует рассмотреть функциюAкод в , проверьте, не содержит ли он слишком много пар функцийBнеобоснованный звонок. Если есть необоснованные вызовы, мы должны переделать эту часть кода. В дополнение к этому мы также можем определить некоторые небольшие проблемы на основе количества вызовов. Например, если функция находится в том же пакете кода, что и все вызывающие ее функции, то нам следует рассмотреть возможность установки прав доступа вызываемой функции как закрытых внутри пакета. Если все вызовы функции исходят из одной и той же функции, мы можем рассмотреть возможность объединения двух функций в соответствии с принципом единой ответственности. Читатель, возможно, заметил, что это противоречит одному из предложений в Процессе 1. По сути, это тоже стратегия отложенной оптимизации.

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

В описании этих процессов анализа мы много раз упоминали команду списка. Теперь давайте объясним это. Сначала рассмотрим пример:

(pprof) list ShowDepStruct
Total: 23 samples
ROUTINE ====================== main.ShowDepStruct in /home/hc/golang/goc2p
	/src/helper/pds/showpds.go
     0     20 Total samples (flat / cumulative)
     .      .   44: 	}
     .      .   45: 	fmt.Printf("The dependency structure of package \'%s\':\n",
	 pkgImportPath)
     .      .   46: 	ShowDepStruct(pn, "")
     .      .   47: }
     .      .   48: 
---
     .      .   49: func ShowDepStruct(pnode *pkgtool.PkgNode, prefix string) {
     .      .   50: 	var buf bytes.Buffer
     .      .   51: 	buf.WriteString(prefix)
     .      .   52: 	importPath := pnode.ImportPath()
     .      2   53: 	buf.WriteString(importPath)
     .      1   54: 	deps := pnode.Deps()
     .      .   55: 	//fmt.Printf("P_NODE: \'%s\', DEP_LEN: %d\n", importPath,
	 len(deps))
     .      .   56: 	if len(deps) == 0 {
     .      5   57: 		fmt.Printf("%s\n", buf.String())
     .      .   58: 		return
     .      .   59: 	}
     .      .   60: 	buf.WriteString(ARROWS)
     .      .   61: 	for _, v := range deps {
     .     12   62: 		ShowDepStruct(v, buf.String())
     .      .   63: 	}
     .      .   64: }
---
     .      .   65: 
     .      .   66: func getPkgImportPath() string {
     .      .   67: 	if len(pkgImportPathFlag) > 0 {
     .      .   68: 		return pkgImportPathFlag
     .      .   69: 	}
(pprof) 
 

Мы ввели команду в интерактивном интерфейсе инструмента pproflist ShowDepStructПолучил много продукции после этого. Среди них ShowDepStruct — это значение параметра подпрограммы_regexp. Первая строка вывода сообщает нам, что в профиле ЦП 23 выборки. Это то же самое, что мы видели ранее, когда объясняли команду gv. Вторая строка вывода показывает, что функция, которая соответствует значению предоставленного нами регулярного выражения программы (то есть параметру подпрограммы_regexp), равнаmain.ShowDepStruct, а абсолютный путь к исходному файлу, в котором находится эта функция, — /home/hc/golang/goc2p/src/helper/pds/showpds.go. Третья строка в выводе говорит нам, что вmain.ShowDepStructСумма локальных выборок для кода в теле функции равна 0, а сумма совокупных выборок равна 20. В крайних правых круглых скобках в третьей строке flat представляет количество локальных выборок, а cumulative — совокупное количество выборок. Это намек на то, что означают два числа в крайнем левом углу строки (то есть 0 и 20). Начиная с четвертой строки выводимой информации идет перехват кода в вышеуказанном файле исходного кода, который содержитmain.ShowDepStructИсходный код функции. Команда list добавляет соответствующие номера строк слева от этих кодов, что облегчает поиск кодов. Кроме того, в соответствующей позиции слева от номера строки кода отображается локальное количество выборок и совокупное количество выборок для каждой строки кода. Если счетчик равен 0, вместо этого используйте английскую точку «.». Это делает очень удобным поиск строк кода, в которых существует значение счетчика.

一般情况下,每行代码对应的本地取样计数和累积取样计数都应该与我们用gv命令生成的函数调用关系图中的计数相同。但是,如果一行代码中存在多个函数调用的话,那么在代码行号左边的计数值就会有偏差。比如,在上述示例中,第62行代码ShowDepStruct(v, buf.String())12 представляет собой совокупное количество образцов. Но из приведенного выше графа вызовов функции мы знаем функциюmain.ShowDepStructСовокупное количество выборок равно 10. Отклонение между ними равно 2. Фактически при выполнении программы строка 62 состоит из двух рабочих шагов. Первым шагом является выполнение вызова функцииbuf.String()и получить результат. Второй шаг — вызов функцииShowDepStruct, а переменнаяv和执行第一个步骤所获得的结果作为参数传入。所以,这2个取样计数应该归咎于第62行代码中的函数调用子句buf.String()。也就是说,第62行代码的累积取样计数由两部分组成,即函数main.ShowDepStruct的累积取样计数和函数bytes.(*Buffer).String的累积取样计数。同理,示例中的第57行代码fmt.Printf("%s\n", buf.String())Существует также систематическая ошибка в кумулятивном подсчете выборки. Читатели могут попытаться проанализировать причины.

Если читатель хочет проверить причину отклонения, упомянутого выше, вы можете разделить 62-ю строку кода и 57-ю строку кода в приведенном выше примере на две строки, а затем скомпилировать и запустить исходный файл команды showpds.go (помните Добавьте соответствующие теги) и просмотрите его с помощью команды списка инструмента pprof. Однако есть более простой способ проверить эту причину — с помощью команды disasm в инструменте pprof. В приведенном ниже примере мы выполняем команду disasm, за которой следует значение параметра подпрограммы_regexp ShowDepStruct.

bash
(pprof) disasm ShowDepStruct
Total: 23 samples
ROUTINE ====================== main.ShowDepStruct
     0     20 samples (flat, cumulative) 87.0% of total
-------------------- /home/hc/mybook/goc2p/src/helper/pds/showpds.go
     .      .    49: func ShowDepStruct(pnode *pkgtool.PkgNode, prefix string) {
<省略部分输出内容>
     .     10    62: ShowDepStruct(v, buf.String())
     .      .     80490ce: MOVL	main.&buf 3c(SP),AX
     .      .     80490d2: XORL	BX,BX
     .      .     80490d4: CMPL	BX,AX
     .      .     80490d6: JNE	main.ShowDepStruct 0x25f(SB)
     .      .     80490d8: LEAL	go.string.* 0x12d4(SB),BX
     .      .     80490de: MOVL	0(BX),CX
     .      .     80490e0: MOVL	4(BX),AX
     .      .     80490e3: MOVL	main.v 48(SP),BX
     .      .     80490e7: MOVL	BX,0(SP)
     .      .     80490ea: MOVL	CX,4(SP)
     .      .     80490ee: MOVL	AX,8(SP)
     .     10     80490f2: CALL	main.ShowDepStruct(SB)
     .      .     80490f7: MOVL	main.autotmp_0046 44(SP),DX
     .      .     80490fb: MOVL	main.autotmp_0048 70(SP),CX
     .      .    61: for _, v := range deps {
     .      .     80490ff: INCL	DX
     .      .     8049100: MOVL	main.autotmp_0047 2c(SP),BX
     .      .     8049104: CMPL	BX,DX
     .      .     8049106: JLT	main.ShowDepStruct 0x20b(SB)
     .      .    64: }
     .      .     8049108: ADDL	$80,SP
     .      .     804910e:    RET
     .      2    62: ShowDepStruct(v, buf.String())
     .      .     804910f: MOVL	8(AX),DI
     .      .     8049112: MOVL	4(AX),DX
     .      .     8049115: MOVL	c(AX),CX
     .      .     8049118: CMPL	CX,DX
     .      .     804911a: JCC	main.ShowDepStruct 0x273(SB)
     .      .     804911c: CALL	runtime.panicslice(SB)
     .      .     8049121:    UD2
     .      .     8049123: MOVL	DX,SI
     .      .     8049125: SUBL	CX,SI
     .      .     8049127: MOVL	DI,DX
     .      .     8049129: SUBL	CX,DX
     .      .     804912b: MOVL	0(AX),BP
     .      .     804912d: ADDL	CX,BP
     .      .     804912f: MOVL	BP,main.autotmp_0073 74(SP)
     .      .     8049133: MOVL	main.autotmp_0073 74(SP),BX
     .      .     8049137: MOVL	BX,0(SP)
     .      .     804913a: MOVL	SI,4(SP)
     .      .     804913e: MOVL	DX,8(SP)
     .      2     8049142: CALL	runtime.slicebytetostring(SB)
<省略部分输出内容>
(pprof) 
 
(pprof) 
 

Из-за недостатка места мы показываем только часть вывода. Вывод команды disasm чем-то похож на вывод команды list. На самом деле команда disasm выводит функциюmain.ShowDepStructВ исходном коде также перечислены инструкции по сборке, соответствующие этой строке кода, под каждой строкой кода. Кроме того, команда также помечает локальный счетчик выборки и кумулятивный счетчик выборки ассемблерной инструкции строки в самой левой соответствующей позиции каждой строки, а английская точка «.» также представляет случай, когда счетчик равен 0. Кроме того, в левой части ассемблерной инструкции, отделённой от ассемблерной инструкции только двоеточием, находится не номер строки, как в строке кода языка Go, а адрес памяти, соответствующий ассемблерной инструкции.

В приведенном выше примере мы фокусируемся только на строке 62 в исходном файле команды showpds.go.ShowDepStruct(v, buf.String())`Соответствующая инструкция по сборке. Читателям настоятельно рекомендуется сосредоточиться на строках с номерами в столбцах, в которых накапливаются значения выборки. Таких строк четыре. Для удобства выделим эти четыре строки следующим образом:

     .     10    62: ShowDepStruct(v, buf.String())
     .     10     80490f2: CALL	main.ShowDepStruct(SB)
     .      2    62: ShowDepStruct(v, buf.String())
     .      2     8049142: CALL	runtime.slicebytetostring(SB)
 

Строки 1 и 3 иллюстрируют состав совокупного количества выборок для строки 62, а строки 2 и 4 объясняют, почему такой состав существует. Среди них инструкция по сборкеCALL main.ShowDepStruct(SB)Совокупное количество выборок равно 10. То есть анализатор взял 10 выборок во время вызова функции main.ShowDepStruct. а инструкция по сборкеruntime.slicebytetostring(SB)Суммарное количество выборок, равное 2, равно 2, что означает, что анализатор взял 2 выборки во время вызова функции runtime.slicebytetostring. но,runtime.slicebytetostringДля чего функция? Фактически,runtime.slicebytetostringфункция есть функцияbytes.(*Buffer).Stringвызов функции. Реализуемая им функция заключается в преобразовании среза, тип элемента которого — byte, в строку. Подводя итог, как мы уже говорили, 62-я строка кода в исходном файле команды showpds.goShowDepStruct(v, buf.String())Совокупное количество выборок, равное 12, состоит из функцииmain.ShowDepStructСовокупное количество выборок, равное 10, и функцияbytes.(*Buffer).String的Совокупное количество выборок состоит из 2.

На данный момент мы представили три очень полезные команды: команду gv, команду list и команду disasm. Мы можем использовать команду gv для графического просмотра локального количества выборок, кумулятивного количества выборок, отношения вызовов и количества вызовов каждой функции в программе, и мы можем легко передать размер области узла и толщину направленного сегмента линии. , Найдите узел с большим значением счетчика. Когда мы обнаруживаем подозрительно трудоемкую функцию в соответствии с описанным выше процессом анализа, мы можем использовать команду list для просмотра локального количества выборок и кумулятивного количества выборок для каждой строки кода внутри функции и можем точно найти чрезмерное использование кода. для процессорного времени. В то же время мы также можем использовать команду disasm для просмотра инструкций по сборке, соответствующих каждой строке кода в функции, и найти источник кода, требующий много времени. Поэтому, пока мы своевременно используем три вышеуказанные команды, мы можем разобраться во всех тонкостях проблем с производительностью программы практически в любой ситуации. Можно сказать, что это швейцарский армейский нож, который язык Go предоставляет нам для решения проблем с производительностью программы.

Однако иногда мы просто хотим понять, какие функции тратят больше всего процессорного времени. В этом случае данные, сгенерированные упомянутыми выше командами, не столь интуитивно понятны. Но не волнуйтесь, инструмент pprof предоставляет для этого команду top. См. пример ниже:

bash
(pprof) top
Total: 23 samples
       5  21.7%  21.7%        5  21.7% runtime.findfunc
       5  21.7%  43.5%        5  21.7% stkbucket
       3  13.0%  56.5%        3  13.0% os.(*File).write
       1   4.3%  60.9%        1   4.3% MHeap_AllocLocked
       1   4.3%  65.2%        1   4.3% getaddrbucket
       1   4.3%  69.6%        2   8.7% runtime.MHeap_Alloc
       1   4.3%  73.9%        1   4.3% runtime.SizeToClass
       1   4.3%  78.3%        1   4.3% runtime.aeshashbody
       1   4.3%  82.6%        1   4.3% runtime.atomicload64
       1   4.3%  87.0%        1   4.3% runtime.convT2E
(pprof) 
 

По умолчанию команда top выводит список в порядке подсчета локальных выборок. Мы можем назвать этот список локальным рейтинговым списком выборки. Каждая строка в списке состоит из шести столбцов. Глядя сейчас слева направо, значения первого и второго столбцов таковы: количество локальных выборок функции и доля количества локальных выборок в общем количестве выборок. Значения четвертого и пятого столбцов: кумулятивное число выборок функции и доля кумулятивного числа выборок в общем числе выборок. Значение пятого столбца — имя функции, соответствующей данным в левых столбцах. Читатели уже должны быть с ними знакомы. Здесь важно отметить третий столбец. Значение третьего столбца — это процент общего числа выборок от суммы локальных значений выборок функции, которая была напечатана до сих пор. В частности, процентное значение в третьей строке и третьем столбце представляет собой сумму трех локальных выборок в первых трех строках списка, деленную на общее количество выборок, равное 23. Мы также можем получить процентное значение в третьей строке, третьем столбце, добавив процентное значение 43,5% во второй строке к процентному значению 13,0% в третьей строке, втором столбце. Процентное значение в третьем столбце позволяет нам интуитивно понять долю процессорного времени, затрачиваемого на наиболее трудоемкие функции. Мы можем использовать это соотношение, чтобы сформулировать более разнообразные цели для задач оптимизации производительности. Например, наша цель оптимизации производительности — уменьшить долю общего времени, затрачиваемого на первые четыре функции, с 60,9 % до 50 % и так далее.

Из приведенного выше примера видно, что функции с большим количеством локальных выборок относятся к пакету кода стандартной библиотеки или внутри языка Go. Поэтому мы не можем или неудобно оптимизировать эти функции. Как мы упоминали ранее, в общем случае локальное количество выборок функции в пользовательской программе будет очень низким или даже равным нулю. Таким образом, если функция, которую мы пишем, находится в верхней части списка ранжирования локального количества выборок, с этой функцией могут быть проблемы с производительностью. На данный момент нам нужно сгенерировать данные для этой функции с помощью команды списка и тщательно их проанализировать. Например, если мы добавим в функцию некоторый код управления параллелизмом (будь то синхронный контроль параллелизма или асинхронный контроль параллелизма), время выполнения самой функции будет очень большим, и она окажется на верхней позиции в рейтинге локального подсчета выборок. ., то мы должны внимательно посмотреть на выборочное количество строк кода в функции и их логическую достоверность. Например, существует ли вероятность взаимоблокировки в коде, используемом для управления синхронным параллелизмом, или есть ли дисбаланс координации или неравномерное распределение ресурсов в коде, используемом для управления асинхронным параллелизмом. Содержание, связанное с написанием разумного и хорошего кода управления параллелизмом, рассматривается в части 3 этой книги.

По умолчанию список, выводимый командой top, содержит только десять первых функций с наибольшим количеством локальных выборок. Если мы хотим настроить количество элементов в этом списке, нам нужно следовать команде top со значением элемента. Например, команда top5 выведет список из 5 строк, команда top20 выведет список из 20 строк и так далее.

Если мы добавим метку после верхней команды--cum, то выходной список находится в порядке совокупного количества выборок. Пример выглядит следующим образом:

(pprof) top20 --cum
Total: 23 samples
       0   0.0%   0.0%       23 100.0% gosched0
       0   0.0%   0.0%       22  95.7% main.main
       0   0.0%   0.0%       22  95.7% runtime.main
       0   0.0%   0.0%       16  69.6% runtime.mallocgc
       0   0.0%   0.0%       12  52.2% pkgtool.(*PkgNode).Grow
       0   0.0%   0.0%       11  47.8% runtime.MProf_Malloc
       0   0.0%   0.0%       10  43.5% main.ShowDepStruct
       0   0.0%   0.0%       10  43.5% pkgtool.getImportsFromPackage
       0   0.0%   0.0%        8  34.8% cnew
       0   0.0%   0.0%        8  34.8% makeslice1
       0   0.0%   0.0%        8  34.8% runtime.cnewarray
       0   0.0%   0.0%        7  30.4% gostringsize
       0   0.0%   0.0%        7  30.4% runtime.slicebytetostring
       0   0.0%   0.0%        6  26.1% pkgtool.getImportsFromGoSource
       0   0.0%   0.0%        6  26.1% runtime.callers
       1   4.3%   4.3%        6  26.1% runtime.gentraceback
       0   0.0%   4.3%        6  26.1% runtime.makeslice
       5  21.7%  26.1%        5  21.7% runtime.findfunc
       5  21.7%  47.8%        5  21.7% stkbucket
       0   0.0%  47.8%        4  17.4% fmt.Fprintf
(pprof) 
 

Мы можем назвать этот тип списка кумулятивным рейтинговым списком выборки. В этом списке перечислены исходный файл команды showpds.go и функции в пакете кода pkgtool. Оба они существуют в проекте goc2p. В реальных сценариях функции в пользовательской программе, как правило, предшествуют графу вызовов функций. Особенно функция входа в исходный файл командыmain.main. Таким образом, их кумулятивное количество выборок, как правило, относительно велико, и неудивительно, что они находятся в верхней части рейтинга кумулятивного количества выборок. Однако, если кумулятивное количество выборок функции и процентная доля велики, это должно быть на что обратить внимание. Это также упоминалось в предыдущем объяснении команды gv. Если мы хотим отфильтровать некоторые функции, которые нам не нужны в списке ранжирования, мы также можем добавить имя одной или нескольких функций, которые мы хотим игнорировать, или соответствующее регулярное выражение в конце команды. так:

(pprof) top20 --cum -fmt\..* -os\..*
Ignoring samples in call stacks that match \'fmt\..*|os\..*\'
Total: 23 samples
After ignoring \'fmt\..*|os\..*\': 15 samples of 23 (65.2%)
       0   0.0%   0.0%       15  65.2% gosched0
       0   0.0%   0.0%       14  60.9% main.main
       0   0.0%   0.0%       14  60.9% runtime.main
       0   0.0%   0.0%       12  52.2% runtime.mallocgc
       0   0.0%   0.0%        8  34.8% pkgtool.(*PkgNode).Grow
       0   0.0%   0.0%        7  30.4% gostringsize
       0   0.0%   0.0%        7  30.4% pkgtool.getImportsFromPackage
       0   0.0%   0.0%        7  30.4% runtime.MProf_Malloc
       0   0.0%   0.0%        7  30.4% runtime.slicebytetostring
       0   0.0%   0.0%        6  26.1% main.ShowDepStruct
       0   0.0%   0.0%        6  26.1% pkgtool.getImportsFromGoSource
       0   0.0%   0.0%        5  21.7% cnew
       0   0.0%   0.0%        5  21.7% makeslice1
       0   0.0%   0.0%        5  21.7% runtime.cnewarray
       0   0.0%   0.0%        4  17.4% runtime.callers
       1   4.3%   4.3%        4  17.4% runtime.gentraceback
       0   0.0%   4.3%        3  13.0% MCentral_Grow
       0   0.0%   4.3%        3  13.0% runtime.MCache_Alloc
       0   0.0%   4.3%        3  13.0% runtime.MCentral_AllocList
       3  13.0%  17.4%        3  13.0% runtime.findfunc
(pprof) 
 

В приведенном выше примере мы получаем информацию о 20 функциях с наибольшим совокупным количеством выборок с помощью команды top20 и отфильтровываем информацию из пакета кода.fmtа такжеosфункция в .

Последняя команда, которую мы подробно рассмотрим, это callgrind. Инструмент pprof может преобразовывать профили в форматы, поддерживаемые Callgrind, компонентом мощного набора инструментов Valgrind. Valgrind — это мощный инструмент, который работает в операционной системе Linux и используется для анализа производительности программ и ошибок утечки памяти в программах. Как один из компонентов, функция Callgrind заключается в сборе некоторых данных, связи вызовов функций и другой информации во время работы программы. Из этого видно, что функция инструмента Callgrind в основном такая же, как и наша предыдущая операция выборки работы программы с использованием API среды выполнения пакета кода стандартной библиотеки.

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

(pprof) callgrind cpu.callgrind
Writing callgrind file to \'cpu.callgrind\'.
(pprof)
 

Файл cpu.callgrind — это обычный текстовый файл, поэтому мы можем использовать любую программу просмотра текста для просмотра его содержимого. Но более удобно то, что мы можем использовать команду callgrind для непосредственного просмотра графических данных. Теперь давайте попробуем:

(pprof) callgrind
Writing callgrind file to \'/tmp/pprof2641.0.callgrind\'.
Starting \'kcachegrind /tmp/pprof2641.0.callgrind & \'
(pprof) sh: 1: kcachegrind: not found
 

Мы не добавляли путь к файлу статистики в качестве аргумента после команды callgrind. Таким образом, команда callgrind будет использовать инструмент kcachegrind для визуального отображения статистики. Однако инструмент kcachegrind еще не установлен в нашей системе.

В дистрибутиве Linux Debian и его производных мы можем напрямую использовать командуsudo apt-get install kcachegrindдля установки инструмента kcachegrind. или мы можем начать сего официальный сайтЗагрузите установочный пакет, чтобы установить его.

После установки инструмента kcachegrind давайте выполним команду callgrind:

bash
(pprof) callgrind
Writing callgrind file to \'/tmp/pprof2641.1.callgrind\'.
Starting \'kcachegrind /tmp/pprof2641.1.callgrind & \'
(pprof) 
 

Как видно из подсказки, выводимой командой, команда callgrind фактически сохраняет файл статистики во временную папку /tmp Linux. Затем используйте инструмент kcachegrind для просмотра. На следующем рисунке показан интерфейс инструмента kcachegrind, который появляется после выполнения команды callgrind в интерактивном режиме инструмента pprof.

使用kcachegrind工具查看概要数据

Рисунок 0-4 Использование инструмента kcachegrind для просмотра сводных данных

Как видно из рисунка выше, инструмент kcachegrind отображает данные очень интуитивно. В целом интерфейс разделен на левую и правую колонки. В левой колонке находится список информации о функциях, записанных в профиле. В списке пять столбцов.Значения слева направо представляют собой процент совокупного количества выборок функции в общем числе выборок, процент локальной выборки функции в общем числе выборок и общее количество выборок. количество вызовов функции (включая рекурсивные вызовы), имя функции и имя исходного файла, в котором находится функция. В правом столбце интерфейса мы просматриваем детали строки, выбранной в левом столбце. Инструмент kcachegrind очень мощный. Однако, поскольку его введение выходит за рамки этой книги, мы ненадолго остановимся здесь.

Мы только что упомянули, что выполнение команды callgrind без каких-либо аргументов делится на два шага — создание файла статистики и просмотр содержимого файла с помощью инструмента kcachegrind. Помните, ранее мы создали файл статистики с именем cpu.callgrind? На самом деле, мы можем использовать командуkcachegrind cpu.callgrindПросмотрите его напрямую. Интерфейс инструмента kcachegrind, который появляется после выполнения этой команды, точно такой же, как мы видели раньше.

На данный момент мы представили еще две команды для более интуитивной статистики и просмотра данных в профилях. Команда top позволяет нам просматривать эту статистику в терминале командной строки. Команда callgrind позволяет просматривать данные профиля с помощью инструмента kcachegrind. Обе эти команды позволяют нам просматривать и анализировать профили на макроуровне и в разных измерениях. Они оба являются очень мощными статистическими средствами.

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

В этом подразделе мы используем толькоgo tool pprofкоманда для просмотра и анализа профиля ЦП. Читатели могут попробовать проанализировать профиль памяти и профиль блокировки программы.

По сравнению с обычным программированием параллельное программирование является сложным. Поэтому нам нужны такие инструменты, как pprof, чтобы сопровождать нас. Вы можете рассматривать этот раздел как документ для инструмента и просматривать его всякий раз, когда вам это нужно.

 

 

 

Ссылка на ссылку:

GitHub.com/hyper0small/go_…