О практической работе
1. Обзор
Хотя разработчики клиентских приложений и веб-интерфейсов обычно используют такие инструменты, как Android Studio CPU Profiler или инструменты профилирования, включенные в Chrome, для повышения производительности своего кода, эквивалентные методы не так доступны и не так хорошо приняты теми, кто работает над серверными службами. Cloud Profiler предоставляет те же возможности разработчикам сервисов, независимо от того, работает ли их код на Google Cloud Platform или где-либо еще.
Инструмент собирает информацию об использовании ЦП и распределении памяти из ваших производственных приложений. Он приписывает эту информацию исходному коду приложения, помогая определить части приложения, потребляющие больше всего ресурсов, и иным образом освещая характеристики производительности кода. Низкие затраты на методы сбора данных, используемые этим инструментом, делают его пригодным для постоянного использования в производственных средах.
В этой лабораторной работе вы узнаете, как настроить Cloud Profiler для программы Go, и познакомитесь с тем, какую информацию о производительности приложений может предоставить этот инструмент.
Что вы узнаете
- Как настроить программу Go для профилирования с помощью Cloud Profiler.
- Как собирать, просматривать и анализировать данные о производительности с помощью Cloud Profiler.
Что вам понадобится
- Проект облачной платформы Google
- Браузер, например Chrome или Firefox.
- Знакомство со стандартными текстовыми редакторами Linux, такими как Vim, EMAC или Nano.
Как вы будете использовать это руководство?
Как бы вы оценили свой опыт работы с Google Cloud Platform?
2. Настройка и требования
Самостоятельная настройка среды
- Войдите в Cloud Console и создайте новый проект или повторно используйте существующий. Если у вас еще нет учетной записи Gmail или Google Workspace, вам необходимо ее создать .
Запомните идентификатор проекта — уникальное имя для всех проектов Google Cloud (имя, указанное выше, уже занято и не подойдет вам, извините!). Позже в этой лаборатории он будет называться PROJECT_ID
.
- Далее вам необходимо включить биллинг в Cloud Console, чтобы использовать ресурсы Google Cloud.
Прохождение этой лаборатории кода не должно стоить много, если вообще стоит. Обязательно следуйте всем инструкциям в разделе «Очистка», в которых рассказывается, как отключить ресурсы, чтобы вам не приходилось нести расходы, выходящие за рамки этого руководства. Новые пользователи Google Cloud имеют право на участие в программе бесплатной пробной версии стоимостью 300 долларов США .
Google Cloud Shell
Хотя Google Cloud можно управлять удаленно с вашего ноутбука, для упрощения настройки в этой лаборатории мы будем использовать Google Cloud Shell , среду командной строки, работающую в облаке.
Активировать Cloud Shell
- В Cloud Console нажмите «Активировать Cloud Shell».
.
Если вы никогда раньше не запускали Cloud Shell, вам будет представлен промежуточный экран (ниже сгиба) с описанием того, что это такое. В этом случае нажмите «Продолжить» (и вы больше никогда этого не увидите). Вот как выглядит этот одноразовый экран:
Подготовка и подключение к Cloud Shell займет всего несколько минут.
Эта виртуальная машина оснащена всеми необходимыми инструментами разработки. Он предлагает постоянный домашний каталог объемом 5 ГБ и работает в Google Cloud, что значительно повышает производительность сети и аутентификацию. Большую часть, если не всю, работу в этой лаборатории кода можно выполнить с помощью просто браузера или Chromebook.
После подключения к Cloud Shell вы увидите, что вы уже прошли аутентификацию и что для проекта уже установлен идентификатор вашего проекта.
- Выполните следующую команду в Cloud Shell, чтобы подтвердить, что вы прошли аутентификацию:
gcloud auth list
Вывод команды
Credentialed Accounts ACTIVE ACCOUNT * <my_account>@<my_domain.com> To set the active account, run: $ gcloud config set account `ACCOUNT`
- Выполните следующую команду в Cloud Shell, чтобы убедиться, что команда gcloud знает о вашем проекте:
gcloud config list project
Вывод команды
[core] project = <PROJECT_ID>
Если это не так, вы можете установить его с помощью этой команды:
gcloud config set project <PROJECT_ID>
Вывод команды
Updated property [core/project].
3. Перейдите в Cloud Profiler.
В Cloud Console перейдите к пользовательскому интерфейсу профилировщика, нажав «Профилировщик» на левой панели навигации:
Альтернативно вы можете использовать панель поиска Cloud Console для перехода к пользовательскому интерфейсу профилировщика: просто введите «Cloud Profiler» и выберите найденный элемент. В любом случае вы должны увидеть пользовательский интерфейс профилировщика с сообщением «Нет данных для отображения», как показано ниже. Проект новый, поэтому данные для его профилирования еще не собраны.
Пришло время что-то профилировать!
4. Профилируйте эталон
Мы будем использовать простое синтетическое приложение Go , доступное на Github . В терминале Cloud Shell, который у вас все еще открыт (и пока сообщение «Нет данных для отображения» все еще отображается в пользовательском интерфейсе профилировщика), выполните следующую команду:
$ go get -u github.com/GoogleCloudPlatform/golang-samples/profiler/...
Затем переключитесь в каталог приложения:
$ cd ~/gopath/src/github.com/GoogleCloudPlatform/golang-samples/profiler/hotapp
Каталог содержит файл «main.go», который представляет собой синтетическое приложение с включенным агентом профилирования:
main.go
...
import (
...
"cloud.google.com/go/profiler"
)
...
func main() {
err := profiler.Start(profiler.Config{
Service: "hotapp-service",
DebugLogging: true,
MutexProfiling: true,
})
if err != nil {
log.Fatalf("failed to start the profiler: %v", err)
}
...
}
Агент профилирования по умолчанию собирает профили ЦП, кучи и потоков. Приведенный здесь код позволяет собирать профили мьютексов (также известных как «конкурентные»).
Теперь запустите программу:
$ go run main.go
Во время работы программы агент профилирования будет периодически собирать профили пяти настроенных типов. Коллекция рандомизирована по времени (со средней скоростью один профиль в минуту для каждого из типов), поэтому сбор каждого из типов может занять до трех минут. Программа сообщит вам, когда она создает профиль. Сообщения включаются флагом DebugLogging
в приведенной выше конфигурации; в противном случае агент запускается автоматически:
$ go run main.go 2018/03/28 15:10:24 profiler has started 2018/03/28 15:10:57 successfully created profile THREADS 2018/03/28 15:10:57 start uploading profile 2018/03/28 15:11:19 successfully created profile CONTENTION 2018/03/28 15:11:30 start uploading profile 2018/03/28 15:11:40 successfully created profile CPU 2018/03/28 15:11:51 start uploading profile 2018/03/28 15:11:53 successfully created profile CONTENTION 2018/03/28 15:12:03 start uploading profile 2018/03/28 15:12:04 successfully created profile HEAP 2018/03/28 15:12:04 start uploading profile 2018/03/28 15:12:04 successfully created profile THREADS 2018/03/28 15:12:04 start uploading profile 2018/03/28 15:12:25 successfully created profile HEAP 2018/03/28 15:12:25 start uploading profile 2018/03/28 15:12:37 successfully created profile CPU ...
Пользовательский интерфейс обновится вскоре после сбора первого профиля. После этого он не будет обновляться автоматически, поэтому, чтобы увидеть новые данные, вам придется обновить пользовательский интерфейс профилировщика вручную. Для этого дважды нажмите кнопку «Сейчас» в выборе временного интервала:
После обновления пользовательского интерфейса вы увидите что-то вроде этого:
Селектор типа профиля показывает пять доступных типов профиля:
Давайте теперь рассмотрим каждый из типов профилей и некоторые важные возможности пользовательского интерфейса, а затем проведем несколько экспериментов. На этом этапе вам больше не нужен терминал Cloud Shell, поэтому вы можете выйти из него, нажав CTRL-C и набрав «exit».
5. Анализ данных профилировщика
Теперь, когда мы собрали некоторые данные, давайте посмотрим на них более внимательно. Мы используем синтетическое приложение (исходный код доступен на Github ), которое имитирует поведение, типичное для различных проблем с производительностью в производстве.
Код, интенсивно использующий процессор
Выберите тип профиля ЦП. После того, как пользовательский интерфейс загрузит его, вы увидите на графике пламени четыре листовых блока для функции load
, которые в совокупности отвечают за все потребление ЦП:
Эта функция специально написана так, чтобы потреблять много циклов процессора за счет выполнения жесткого цикла:
main.go
func load() {
for i := 0; i < (1 << 20); i++ {
}
}
Функция вызывается косвенно из busyloop
() через четыре пути вызова: busyloop
→ { foo1
, foo2
} → { bar
, baz
} → load
. Ширина функционального поля представляет собой относительную стоимость конкретного пути вызова. В этом случае все четыре пути имеют примерно одинаковую стоимость. В реальной программе вы хотите сосредоточиться на оптимизации путей вызовов, которые наиболее важны с точки зрения производительности. Пламенный график, который визуально выделяет более дорогие пути более крупными прямоугольниками, позволяет легко идентифицировать эти пути.
Вы можете использовать фильтр данных профиля для дальнейшего уточнения отображения. Например, попробуйте добавить фильтр «Показать стеки», указав в качестве строки фильтра «baz». Вы должны увидеть что-то вроде снимка экрана ниже, где отображаются только два из четырех путей вызова метода load()
. Эти два пути — единственные, которые проходят через функцию со строкой «baz» в имени. Такая фильтрация полезна, когда вы хотите сосредоточиться на части более крупной программы (например, потому что вы владеете только ее частью).
Код, интенсивно использующий память
Теперь переключитесь на тип профиля «Куча». Обязательно удалите все фильтры, созданные вами в предыдущих экспериментах. Теперь вы должны увидеть график пламени, где allocImpl
, вызываемый alloc
, отображается как основной потребитель памяти в приложении:
Сводная таблица над графиком пламени показывает, что общий объем используемой памяти в приложении составляет в среднем ~57,4 МБ, большая часть которой выделяется функцией allocImpl
. Это неудивительно, учитывая реализацию этой функции:
main.go
func allocImpl() {
// Allocate 64 MiB in 64 KiB chunks
for i := 0; i < 64*16; i++ {
mem = append(mem, make([]byte, 64*1024))
}
}
Функция выполняется один раз, выделяя 64 МБ на более мелкие фрагменты, а затем сохраняя указатели на эти фрагменты в глобальной переменной, чтобы защитить их от сбора мусора. Обратите внимание, что объем памяти, используемый профилировщиком, немного отличается от 64 МБ: профилировщик кучи Go — это статистический инструмент, поэтому измерения выполняются с низкими издержками, но не с точностью до байта. Не удивляйтесь, увидев такую разницу в ~10%.
Код с интенсивным вводом-выводом
Если вы выберете «Потоки» в селекторе типа профиля, отображение переключится на график пламени, где большую часть ширины занимают функции wait
и waitImpl
:
В сводном графике пламени выше вы можете видеть, что существует 100 горутин, которые увеличивают свой стек вызовов из функции wait
. Это совершенно верно, учитывая, что код, инициирующий эти ожидания, выглядит так:
main.go
func main() {
...
// Simulate some waiting goroutines.
for i := 0; i < 100; i++ {
go wait()
}
Этот тип профиля полезен для понимания того, проводит ли программа какое-либо неожиданное время ожидания (например, ввода-вывода). Такие стеки вызовов обычно не выбираются профилировщиком ЦП, поскольку они не потребляют значительной части процессорного времени. Вы часто захотите использовать фильтры «Скрыть стеки» с профилями потоков — например, чтобы скрыть все стеки, заканчивающиеся вызовом gopark,
поскольку они часто являются простаивающими горутинами и менее интересны, чем те, которые ожидают ввода-вывода.
Тип профиля потоков также может помочь определить точки в программе, где потоки ожидают мьютекс, принадлежащий другой части программы, в течение длительного периода, но для этого более полезен следующий тип профиля.
Конфликтный код
Тип профиля конкуренции определяет наиболее «желательные» блокировки в программе. Этот тип профиля доступен для программ Go, но его необходимо явно включить, указав « MutexProfiling: true
» в коде конфигурации агента. Коллекция работает путем записи (в метрике «Конфликты») количества раз, когда конкретная блокировка при разблокировке горутиной A заставляла другую горутину B ожидать разблокировки блокировки. Он также записывает (в метрике «Задержка») время, в течение которого заблокированная горутина ждала блокировки. В этом примере имеется один конкурирующий стек, а общее время ожидания блокировки составило 10,5 секунд:
Код, генерирующий этот профиль, состоит из 4 горутин, борющихся за мьютекс:
main.go
func contention(d time.Duration) {
contentionImpl(d)
}
func contentionImpl(d time.Duration) {
for {
mu.Lock()
time.Sleep(d)
mu.Unlock()
}
}
...
func main() {
...
for i := 0; i < 4; i++ {
go contention(time.Duration(i) * 50 * time.Millisecond)
}
}
6. Краткое содержание
В ходе этой лабораторной работы вы узнали, как настроить программу Go для использования с Cloud Profiler. Вы также узнали, как собирать, просматривать и анализировать данные о производительности с помощью этого инструмента. Теперь вы можете применить свои новые навыки к реальным сервисам, которые вы используете на Google Cloud Platform.
7. Поздравляем!
Вы узнали, как настроить и использовать Cloud Profiler!
Узнать больше
- Облачный профилировщик: https://cloud.google.com/profiler/
- Пакет Go runtime/pprof, который использует Cloud Profiler: https://golang.org/pkg/runtime/pprof/
Лицензия
Эта работа распространяется под лицензией Creative Commons Attribution 2.0 Generic License.