Цель статьи — обсудить 6 советов, которые могут помочь в диагностике и устранении проблем с производительностью в ваших приложениях Go.
Сравнительный анализ:
Написание эффективных тестов на Go имеет решающее значение для понимания производительности вашего кода. Тесты можно создать, добавив суффикс «_test» к файлу Go и используя функцию Benchmark пакета тестирования. Вот пример:
В этом примере мы оцениваем время, необходимое для вычисления 20-го числа Фибоначчи. Функция BenchmarkFibonacci
запускает функцию fibonacci
b.N
раза, что является значением, установленным пакетом тестирования для получения статистически значимого результата.
Чтобы интерпретировать результаты тестов, мы можем запустить go test -bench=. -benchmem
в терминале, который выполнит все тесты в текущем каталоге и распечатает статистику распределения памяти. Флаг -bench
используется для указания регулярного выражения для сопоставления имен тестов, а .
будет соответствовать всем тестам в текущем каталоге. Флаг -benchmem
будет печатать статистику выделения памяти вместе с результатами синхронизации.
Профилирование:
В Go есть встроенные инструменты профилирования, которые помогут вам понять, что делает ваш код. Наиболее распространенным инструментом профилирования является профилировщик ЦП, который можно включить, добавив флаг -cpuprofile
к команде go test
. Вот пример:
Первая функция, «TestFibonacci», представляет собой простой модульный тест, который проверяет, правильно ли функция фибоначчи возвращает 20-е число в последовательности фибоначчи.
Функция «фибоначчи» — это рекурсивная реализация последовательности фибоначчи, которая вычисляет n-е число в последовательности.
Функция «BenchmarkFibonacci» — это тест, который запускает функцию «Fibonacci» 20 раз и измеряет время выполнения.
Функция «ExampleFibonacci» — это пример, который печатает 20-е число в последовательности фибоначчи с помощью функции «фибоначчи» и проверяет, равно ли оно ожидаемому значению 6765.
Чтобы включить профилирование, мы используем флаг «-cpuprofile» с командой «go test», чтобы вывести результаты профилирования в файл с именем «prof.out». Для запуска тестов и создания данных профилирования можно использовать следующую команду:
После запуска тестов мы можем использовать команду «go tool pprof» для анализа данных профилирования. Мы можем запустить инструмент pprof с помощью следующей команды:
Это откроет интерактивную оболочку pprof, где мы можем вводить различные команды для анализа данных профилирования. Например, мы можем использовать команду «top», чтобы отобразить функции, потребляющие больше всего процессорного времени:
Это отобразит список функций с наибольшим использованием процессорного времени, отсортированных по процессорному времени. В этом случае мы должны увидеть функцию «фибоначчи» вверху списка, так как она потребляла больше всего процессорного времени во время теста.
Мы также можем использовать команду «web» для отображения данных профилирования в графическом формате и команду «list» для отображения исходного кода, аннотированного данными профилирования.
Профилирование — это мощный инструмент, который может помочь нам определить узкие места производительности в нашем коде. Используя флаг «-cpuprofile» и инструмент pprof, мы можем легко генерировать и анализировать данные профилирования для наших тестов и приложений Go.
Оптимизация компилятора:
Компилятор Go выполняет несколько оптимизаций, включая встраивание, escape-анализ и удаление мертвого кода. Встраивание — это процесс замены вызова функции телом функции, что может повысить производительность за счет уменьшения накладных расходов на вызов функции. Escape-анализ — это процесс определения того, занят ли адрес переменной, что может помочь компилятору разместить его в стеке, а не в куче. Устранение мертвого кода — это процесс удаления кода, который никогда не выполняется.
Встраивание:
В первом примере функция add
вызывается с аргументами 3
и 4
, что приводит к накладным расходам на вызов функции. Во втором примере вызов функции заменяется фактическим кодом функции, что приводит к более быстрому выполнению.
Анализ побега:
В этом примере в стеке выделяется переменная a
, так как ее адрес не занят. Однако в куче выделяется переменная b
, так как ее адрес берется оператором &
.
Подробнее об анализе побегов:
В функции createUser
создается новый User
и возвращается его адрес. Обратите внимание, что значение User
размещается в стеке с момента возврата его адреса, поэтому оно не уходит в кучу.
Если мы добавим строку, которая принимает адрес значения User
перед его возвратом:
Теперь адрес значения User
берется и сохраняется в возвращаемой переменной. Это приводит к тому, что значение уходит в кучу, а не распределяется в стеке.
Анализ побега важен, потому что выделение кучи дороже, чем выделение стека, поэтому минимизация выделения кучи может повысить производительность.
Устранение мертвого кода:
В этом примере код внутри оператора if
никогда не выполняется, поэтому он удаляется компилятором при устранении мертвого кода.
Понимание Execution Tracer:
Трассировщик выполнения в Go предоставляет подробную информацию о том, что происходит в программе, включая трассировку стека, блокировку горутин и многое другое. Вот пример того, как его использовать:
В этом примере мы создаем файл трассировки, запускаем трассировку, а затем останавливаем трассировку. При запуске программы данные трассировки будут записаны в файл trace.out
. Затем вы можете проанализировать эти данные трассировки, чтобы лучше понять, что происходит в вашей программе.
Управление памятью и настройка GC:
В Go сборка мусора выполняется автоматически и управляется средой выполнения. Однако есть несколько способов настроить сборщик мусора для повышения производительности. Вот пример того, как установить некоторые параметры сборщика мусора:
В этом примере мы устанавливаем максимальное количество используемых ЦП, минимальный размер кучи и процент сбора мусора. Эти параметры можно изменить для повышения производительности в зависимости от потребностей вашей программы.
Чтобы узнать больше, отметьте Go Advanced Topics Deep Dive — Garbage Collector.
Параллелизм:
Go имеет встроенную поддержку параллелизма через горутины и каналы. Однако важно правильно использовать эти функции, чтобы избежать таких проблем, как условия гонки и взаимоблокировки. Вот пример того, как использовать каналы для безопасного общения между горутинами:
Оператор make(chan int)
создает канал, который используется для передачи целочисленного значения между двумя горутинами.
Первая горутина создается с помощью оператора go func() {...}()
, который отправляет значение 1 в канал ch
после ожидания в течение 1 секунды. Это означает, что через 1 секунду канал ch
будет иметь значение 1.
Вторая горутина создается с помощью инструкции select
, которая ожидает связи по каналу ch
. Если из канала получено значение, печатается сообщение «Получено сообщение». Если значение не получено в течение 2 секунд, печатается сообщение «Время истекло».
Таким образом, хотя между оператором select
и первой горутиной нет явной связи, все же связь происходит через общий канал ch
.
Наконец:
Если вам понравилась эта статья, пожалуйста, подпишитесь или подпишитесь, чтобы вовремя получать высококачественный контент. Спасибо за Вашу поддержку ;)