Как оптимизировать Python-скрипты для обработки больших данных

Работа с большими данными в Python — это всегда баланс между скоростью, памятью и читаемостью кода. Недавно я столкнулся с задачей обработки логов веб-сервера размером в несколько гигабайт, и, честно говоря, мой первый скрипт работал так медленно, что я успел заварить три чашки кофе, пока он завершился. В этой статье поделюсь несколькими приемами, которые помогли мне ускорить обработку данных, не усложняя код до состояния «кто это вообще писал?».

1. Используйте генераторы вместо списков

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

# Плохо: загружаем весь файл в память
with open('huge_log.txt', 'r') as file:
    lines = file.readlines()
    for line in lines:
        process(line)

# Хорошо: читаем построчно
with open('huge_log.txt', 'r') as file:
    for line in file:
        process(line)

Генераторы позволяют обрабатывать данные по одной записи за раз, экономя память. Это особенно полезно, если вы фильтруете или трансформируете данные.

2. Pandas с умом: chunksize и dtypes

Pandas — отличный инструмент, но если вы просто вызовете pd.read_csv('big_file.csv'), можете попрощаться с производительностью. Для больших файлов используйте параметр chunksize, чтобы читать данные частями.

import pandas as pd

for chunk in pd.read_csv('big_file.csv', chunksize=10000):
    # Обрабатываем кусок данных
    process_chunk(chunk)

Еще один лайфхак — задавайте типы данных (dtypes) заранее. Например, если вы знаете, что столбец содержит только целые числа, укажите int32 вместо стандартного float64. Это сократит использование памяти в разы.

dtypes = {'user_id': 'int32', 'score': 'float32'}
df = pd.read_csv('big_file.csv', dtype=dtypes)

3. Многопроцессность с multiprocessing

Если ваш скрипт обрабатывает данные независимо (например, применяет функцию к каждой строке), попробуйте распараллелить задачу с помощью модуля multiprocessing. В моем случае разбиение обработки логов на несколько процессов сократило время выполнения с 20 минут до 5.

from multiprocessing import Pool

def process_line(line):
    # Логика обработки строки
    return result

with open('huge_log.txt', 'r') as file:
    lines = list(file)  # Для примера, в реальной задаче лучше генератор
    with Pool(processes=4) as pool:
        results = pool.map(process_line, lines)

Но будьте осторожны: многопроцессность увеличивает нагрузку на CPU, и для задач, ограниченных вводом-выводом (I/O), она может не дать прироста.

4. NumPy для числовых операций

Если вы работаете с числовыми данными, забудьте про циклы в Python. NumPy выполняет операции над массивами на порядок быстрее благодаря векторизации. Например, вместо:

results = []
for x in data:
    results.append(x * 2)

Используйте:

import numpy as np
results = np.array(data) * 2

В моем проекте замена цикла на NumPy для подсчета метрик сократила время обработки с 30 секунд до 2.

5. Профилирование — ваш лучший друг

Иногда вы оптимизируете не то, что нужно. Перед тем как переписывать код, используйте профилировщик, чтобы найти узкие места. Я люблю line_profiler (нужно установить через pip и добавить декоратор @profile к функции). Он показал, что в моем скрипте 80% времени уходило на парсинг JSON в каждой строке лога. Переход на ujson вместо стандартного json ускорил процесс в 3 раза.

Итог

Оптимизация скриптов — это не магия, а методичный подход: минимизируйте использование памяти, распараллеливайте задачи, используйте правильные инструменты и всегда профилируйте код. Мой скрипт для логов теперь работает за 5 минут вместо часа, и я даже успеваю выпить кофе, пока он выполняется. Какие приемы оптимизации используете вы? Делитесь в комментариях на сайте!