Использование профилирования в Elixir для повышения производительности

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

Вот тут-то и приходит на помощь профилирование.

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

В этой заметке мы рассмотрим три инструмента профилирования в Elixir: cprof, eprof и fprof.

Нужно ли мне профилирование для моего приложения Elixir?

Во-первых, я хочу пояснить, что профилирование может и не понадобиться. Как писал Дональд Кнут: «преждевременная оптимизация — корень всех зол». Некоторые операции просто нельзя оптимизировать.

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

С этим разобрались, давайте перейдем к вариантам профилирования.

Профилирование с помощью cprof в Elixir

cprof подсчитывает количество вызовов каждой функции.
Он не профилирует время, проведенное в этих функциях, поэтому он имеет наименьшие накладные расходы.

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

Давайте посмотрим, как с помощью cprof можно профилировать генератор чисел Фибоначчи.

Вы можете следить за кодом. Он использует алгоритм Rosetta Code для генерации последовательности Фибоначчи.

Войдите в сессию IEx и запустите cprof следующим образом:

iex(15)> :cprof.start()
19244
iex(16)> FibonacciRosettaCode.list(10)
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
iex(17)> :cprof.analyse(FibonacciRosettaCode)
{FibonacciRosettaCode, 79,
 [
   {{FibonacciRosettaCode, :fibonacci, 3}, 54},
   {{FibonacciRosettaCode, :fibonacci, 1}, 11},
   {{FibonacciRosettaCode, :"-fun.fibonacci/1-", 1}, 11},
   {{FibonacciRosettaCode, :module_info, 1}, 1},
   {{FibonacciRosettaCode, :list, 1}, 1},
   {{FibonacciRosettaCode, :__info__, 1}, 1}
 ]}
iex(18)> :cprof.stop()
19266
Войти в полноэкранный режим Выйти из полноэкранного режима

Сначала запустите cprof, запустите код, который вы хотите профилировать, а затем вызовите :cprof.analyse/1 для получения статистики для данного модуля. Внутри модуля :cprof доступно несколько других опций, например, приостановка профилирования с помощью :cprof.pause/0.

Мы также можем напрямую использовать обертку задачи profile.cprof от Mix.

➜ mix profile.cprof -e "FibonacciRosettaCode.list(10)"
                                                                     CNT
Total                                                                113
FibonacciRosettaCode                                                  77  <--
  FibonacciRosettaCode.fibonacci/3                                    54
  FibonacciRosettaCode.fibonacci/1                                    11
  FibonacciRosettaCode."-fun.fibonacci/1-"/1                          11
  FibonacciRosettaCode.list/1                                          1
Enum                                                                  24  <--
  Enum.reduce_range/5                                                 12
  anonymous fn/3 in Enum.map/2                                        11
  Enum.map/2                                                           1
  ...
Вход в полноэкранный режим Выход из полноэкранного режима

Здесь мы видим, что функция fibonacci/3 вызывается чаще всего.

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

eprof Профилирование в Elixir

cprof измеряет количество вызовов, а eprof измеряет время выполнения (в дополнение к количеству вызовов), проведенное внутри каждой функции.
Он имеет немного больше накладных расходов, чем cprof. Используйте eprof, когда вы хотите найти наиболее трудоемкие функции.

Использование очень похоже на cprof. Вам просто нужно start профилировщика, запустить код, который вы хотите профилировать, а затем вызвать analyze для получения результатов.

Давайте проанализируем результаты задачи mix profile.eprof на нашем примере генератора Фибоначчи.

➜ mix profile.eprof -e "FibonacciRosettaCode.list(10)"
#                                               CALLS     % TIME µS/CALL
Total                                             106 100.0   45    0.42
anonymous fn/0 in :elixir_compiler_1.__FILE__/1     1  0.00    0    0.00
:lists.reverse/1                                    1  2.22    1    1.00
:lists.reverse/2                                    1  2.22    1    1.00
FibonacciRosettaCode.fibonacci/1                   11  4.44    2    0.18
Enum.map/2                                          1  4.44    2    2.00
Range.new/2                                         1  4.44    2    2.00
FibonacciRosettaCode."-fun.fibonacci/1-"/1         11  8.89    4    0.36
FibonacciRosettaCode.list/1                         1  8.89    4    4.00
:erlang.apply/2                                     1  8.89    4    4.00
anonymous fn/3 in Enum.map/2                       11 13.33    6    0.55
Enum.reduce_range/5                                12 13.33    6    0.50
FibonacciRosettaCode.fibonacci/3                   54 28.89   13    0.24
Вход в полноэкранный режим Выход из полноэкранного режима

Здесь мы видим, что fibonacci/3 снова является самой трудоемкой частью нашей программы, потребляя 28,89% от общего времени выполнения и 0,24 мкс на вызов.

Кроме того, eprof также позволяет нам профилировать вызовы функций в различных процессах.

Использование очень простое. Рассмотрим пример:

:eprof.start_profiling([self()])

# Do some work
1..100 |> Enum.each(fn i ->
  spawn(fn -> FibonacciRosettaCode.list(i + 1) end)
end)

:eprof.stop_profiling()
:eprof.analyze()
Вход в полноэкранный режим Выход из полноэкранного режима

Вам нужно вызвать start_profiling/1 со списком процессов для профилирования.

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

Когда вы закончите, вызовите stop_profiling и запустите analyze, чтобы получить результаты. Они должны выглядеть примерно так, с записью для каждого процесса и процентом времени, которое он был занят:

****** Process <0.150.0>    -- 37.26 % of profiled time ***
FUNCTION                                                             CALLS        %   TIME  [uS / CALLS]
--------                                                             -----  -------   ----  [----------]
io:request/2                                                             3     0.00      0  [      0.00]
...

****** Process <0.277.0>    -- 0.05 % of profiled time ***
FUNCTION                                             CALLS        %  TIME  [uS / CALLS]
--------                                             -----  -------  ----  [----------]
'Elixir.FibonacciRosettaCode':fibonacci/3                2     0.00     0  [      0.00]
'Elixir.FibonacciRosettaCode':fibonacci/1                3     3.03     1  [      0.33]
'Elixir.FibonacciRosettaCode':'-fun.fibonacci/1-'/1      3     3.03     1  [      0.33]
...
Вход в полноэкранный режим Выход из полноэкранного режима

Самое замечательное в этой программе то, что вы можете запустить ее в производственных системах, получив доступ к удаленной консоли. Просто вызовите start_profiling/1, подождите некоторое время для обработки запросов или вручную запустите некоторые запросы, которые вы хотите профилировать. Затем вызовите stop_profiling, а затем analyze, чтобы получить результаты.

Использование fprof в Elixir

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

Затем вы можете передать этот файл трассировки в другие инструменты для тщательной визуализации результатов или использовать :fprof.analyse, как описано выше, для получения количества функций и времени их выполнения.

Запуск fprof через IEx довольно сложен по сравнению с запуском cprof и eprof, но fprof позволяет отслеживать несколько процессов.

Вот как можно сгенерировать файл трассировки для всех процессов:

:fprof.start()
:fprof.trace([:start, procs: :all])

# Do some work
1..100 |> Enum.each(fn i ->
  spawn(fn -> FibonacciRosettaCode.list(i + 1) end)
end)

:fprof.trace(:stop)
:fprof.profile()
:fprof.analyse(totals: false, dest: 'prof.analysis')
Войдите в полноэкранный режим Выйти из полноэкранного режима

Это генерирует полную трассировку всего, что происходило во время запуска:

Вот полный файл (50 МБ), если вам интересно.

Как и eprof, fprof — отличный инструмент для профилирования приложений непосредственно в производстве. Обратите внимание, что это значительно замедлит работу приложения, поэтому будьте готовы к снижению производительности во время профилирования и огромному файлу трассировки.

Итак, теперь у вас есть данные. Если вы чувствуете себя авантюристом, вы можете начать копаться в файле и искать закономерности.

Или вы можете использовать некоторые инструменты для визуализации данных.
Популярным из них является erlgrind. Он конвертирует файл fprof в формат cgrind, который можно открыть внутри KCachegrind.

Чтобы преобразовать файл, загрузите erlgrind и затем запустите src/erlgrind profile.fprof.

В результате будет создан файл prof.cgrind, который можно открыть, чтобы увидеть графики, подобные этому:

Профиль и сбор показателей вашего приложения Elixir в производстве

eprof и fprof могут помочь в профилировании вашего приложения в производстве, но есть еще несколько инструментов, о которых стоит упомянуть — Phoenix Live Dashboard и AppSignal.

Phoenix Live Dashboard

Живая приборная панель Phoenix может обеспечить отличный быстрый обзор метрик. Хотя это не совсем профилировщик, он показывает данные ОС и метрики из событий телеметрии и процессов, среди прочего.

Вот пример приборной панели в действии:

AppSignal

AppSignal — еще один отличный инструмент для сбора данных о производительности (среди прочего). Добавление AppSignal в существующее приложение занимает несколько секунд.

Просто установите зависимость appsignal и запустите смешанную задачу appsignal.install с ключом API. Он имеет хороший набор метрик по умолчанию, включая пропускную способность и время отклика для приложения. Вы даже можете настроить минутные зонды для отслеживания пользовательских метрик.

Вот пример приборной панели AppSignal, собирающей данные из образца приложения:

Если вы хотите собрать данные о конкретных блоках кода, которые, по вашему мнению, работают медленно, функция инструментария AppSignal может помочь собрать эти данные. Просто оберните подозрительный код внутри Appsignal.instrument/2, как показано ниже:

defmodule MyApp.PageController do
  use MyApp, :controller

  def index(conn, _params) do
    custom_function()
    render(conn, "index.html")
  end

  defp custom_function do
    Appsignal.instrument("custom_function", fn ->
      :timer.sleep(1000)
    end)
  end
end
Вход в полноэкранный режим Выйти из полноэкранного режима

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

Ознакомьтесь с полным списком возможностей в документации AppSignal.

Подведение итогов: Измерьте производительность вашего приложения Elixir с помощью профилирования

В этом посте мы рассмотрели, как встроенные инструменты, такие как cprof, eprof и fprof, могут помочь собрать информацию о производительности вашего кода даже во время производства.

Мы также рассмотрели пару других инструментов для мониторинга и трассировки приложений: Phoenix Live Dashboard и AppSignal.

До следующего раза, счастливого профилирования!

P.S. Если вы хотите читать посты Elixir Alchemy сразу после их выхода, подпишитесь на нашу рассылку и не пропустите ни одного поста!

Оцените статью
Procodings.ru
Добавить комментарий