Python — отличный язык программирования. Это мой любимый язык для всех проектов по анализу данных и моделированию. Но интересно отметить, что многие основные фреймворки для Python используют в своей основе компилируемый язык низкого уровня. На самом деле, Python — это простой язык программирования для изучения и написания кода, но он не является эталоном производительности.

Здесь мы собираемся показать решение, использующее несколько процессов Python, которые могут ускорить выполнение циклов в некоторых ситуациях.

Проблема

Мы можем спорить о многом… Python — язык с динамической типизацией, он интерпретируется, а не компилируется и так далее. Но давайте сосредоточимся на проблеме, которую мы можем решить с помощью (ниже) решения в этой статье: самые популярные реализации Python не могут поддерживать параллельное выполнение кода.

Несмотря на то, что Python поддерживает потоки, мы фактически ограничены одной строкой выполнения виртуальной машины Python. И в этом мы можем обвинить GIL (Global Interpreter Lock).

GIL — это мьютекс, защищающий доступ к объектам Python в реализации CPython. Другими словами, это гарантирует, что все объекты работают без проблем с синхронизацией, но за счет предотвращения одновременного выполнения нескольких потоков байт-кода Python.

Решение

Если мы ограничены одной строкой выполнения кода на виртуальной машине Python, что мы можем сделать?
Вот именно! Давайте запустим наш код на нескольких виртуальных машинах Python!

Давайте создадим игрушечную задачу для примера. Мы хотим выполнить функцию обработки множества фрагментов данных. Хотя есть хорошие решения, например, с pandas Data Frame, загрузка множества небольших файлов может быть медленной, а к их содержимому применяется интенсивная обработка.

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

output: We did it in 13.80 seconds

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

output: We did it in 4.24 seconds (using joblib!)

Вау! Он использовал треть времени нашего кода версии цикла!
Давайте на минутку посмотрим, что мы сделали.

joblib.Parallel — это класс, в котором происходит волшебство. Он принимает некоторые параметры, которые будут управлять его поведением. Здесь мы используем только два из них:

  • n_jobs: укажите количество процессов Python для запуска наших заданий (как правило, n_jobs = количество ядер ЦП).
  • verbose: уровень детализации

Вы можете проверить все параметры на странице документации: joblib.Parallel

joblib.delayed — это декоратор, используемый в качестве трюка для создания кортежа (функция, аргументы, kwarg), который можно вызывать в синтаксисе понимания списка.

Вывод

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

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