Привет, Хабр! Сегодня разберемся с одной важной темой, которая может серьезно улучшить производительность Python‑кода — параллельные вычисления с помощью Joblib.
Joblib — это Python‑библиотека, которая предоставляет инструменты для параллельных вычислений, кэширования и эффективной обработки данных. Она используется для ускорения выполнения операций, таких как многократные вычисления, обработка больших массивов данных и параллельная обработка однотипных задач.
Почему стоит использовать Joblib вместо стандартного multiprocessing?
Простота: Joblib значительно упрощает код и скрывает множество деталей, связанных с многозадачностью.
Меньше накладных расходов: для некоторых операций Joblib может быть быстрее, чем использование дефолтных механизмов Python (например,
multiprocessing).Меньше кода: Joblib позволяет просто распараллелить вычисления с минимальными усилиями.
Параллельные вычисления с Joblib
Начнем с самого простого — распараллеливания вычислений. Представим, что есть массив данных, и нужно применить одну и ту же функцию к каждому элементу массива.
Допустим, есть задача вычислить квадратный корень для каждого элемента из списка чисел. Без параллельных вычислений это будет работать последовательно:
from math import sqrt data = [i for i in range(1000000)] result = [sqrt(i ** 2) for i in data]
Это нормально, но можно ли ускорить процесс? С помощью Joblib можно распараллелить этот цикл.
Вот как это выглядит:
from joblib import Parallel, delayed from math import sqrt # Данные data = [i for i in range(1000000)] # Параллельная обработка result = Parallel(n_jobs=4)(delayed(sqrt)(i ** 2) for i in data) print(result[:10]) # Печатаем первые 10 результатов
n_jobs=4: говорим, что хотим использовать 4 ядра процессора для выполнения задачи параллельно.delayed(sqrt): функцияdelayedпросто оборачивает функцию, чтобы её можно было передавать в параллельные вычисления.
Это распараллеливает вычисление и ускоряет его.
Важно помнить, что Joblib использует множество процессов, и каждый процесс работает в своем собственном пространстве памяти. Это важно учитывать, когда у вас есть ограничения по памяти. Если задача использует много памяти, распараллеливание может привести к увеличению нагрузки на систему, а в некоторых случаях даже к её замедлению.
Использование потоков или процессов?
Мы рассмотрели использование процессов, но что, если ваша задача не связана с тяжелыми вычислениями, а, например, работает с I/O или сетевыми запросами? В таком случае использование потоков будет более подходящим, т.к Python позволяет эффективно работать с потоками в таких сценариях.
Пример с потоками:
from joblib import Parallel, delayed import time # Пример задачи с I/O операциями def fetch_data(i): time.sleep(1) return i # Параллельная обработка с потоками result = Parallel(n_jobs=4, prefer="threads")(delayed(fetch_data)(i) for i in range(10)) print(result)
Указываем prefer="threads", чтобы использовать потоки, а не процессы. Это важно, если задача не требует интенсивных вычислений, а скорее связана с ожиданием.
Работа с большими данными и меммапы
Когда данные не помещаются в память, необходимо использовать memory‑mapped files. Это позволяет работать с данными, не загружая их целиком в память. Joblib позволяет легко работать с такими данными.
Пример использования меммапов:
import numpy as np from joblib import Parallel, delayed import os import tempfile # Создаем временную директорию для хранения данных temp_folder = tempfile.mkdtemp() # Создаем массив данных large_data = np.random.rand(int(1e8)) # Массив из 100 миллионов элементов # Сохраняем данные с использованием меммапа filename = os.path.join(temp_folder, "large_data.mmap") np.save(filename, large_data) # Загружаем данные через меммап memmap_data = np.load(filename, mmap_mode='r') # Параллельная обработка меммапа result = Parallel(n_jobs=4)(delayed(np.sum)(memmap_data[i:i+10000]) for i in range(0, len(memmap_data), 10000)) print(result[:5]) # Печатаем первые несколько результатов
Здесь создаём массив с огромным количеством данных и используем меммап для того, чтобы не загружать его целиком в память.
Кэширование вычислений
Теперь поговорим о кэшировании. Когда задача повторяется, зачем снова вычислять одно и то же? Joblib позволяет легко кэшировать результаты вычислений, чтобы избежать повторной работы.
Пример кэширования:
from joblib import Memory # Создаем кэш memory = Memory("/tmp/joblib_cache", verbose=0) @memory.cache def expensive_computation(x): print(f"Выполняется сложная операция для {x}") return x ** 2 # Вызываем функцию print(expensive_computation(10)) print(expensive_computation(10)) # Будет взят из кэша
Когда вы повторно вызываете expensive_computation(10), Joblib не будет повторно вычислять результат, а вернёт его из кэша. Это позволяет существенно сэкономить время, если у вас дорогостоящие вычисления.
А более подробно с библиотекой можно ознакомиться здесь.
Статья подготовлена в рамках специализации Python Developer, на которой можно прокачать навыки разработки на Python с нуля и до middle+ уровня. Ознакомиться с полной программой, а также посмотреть записи открытых уроков можно на странице специализации.
