Цель статьи — обсудить 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.

Наконец:

Если вам понравилась эта статья, пожалуйста, подпишитесь или подпишитесь, чтобы вовремя получать высококачественный контент. Спасибо за Вашу поддержку ;)

Ссылка:

https://dave.cheney.net/high-performance-go