Собесов

Сценарий: профайлинг медленного pandas-пайплайна

PythonВекторизация и performanceСредняяMiddle

Условие

Скрипт обрабатывает 50 млн строк за 25 минут. Менеджер просит ускорить. Как методично найти узкое место?

Решение

План

  1. Сначала измерить, не угадывать.
  2. Локализовать самую медленную строку.
  3. Понять, чем именно — CPU или памятью.
  4. Оптимизировать только её.

Инструменты

# 1. Сколько занимает каждый шаг — %time / %timeit в Jupyter
%time result = step1(df)
 
# 2. Построчно — line_profiler
%load_ext line_profiler
%lprun -f my_func my_func(df)
 
# 3. Память — memory_profiler / df.memory_usage(deep=True)
df.memory_usage(deep=True).sum() / 1e9, 'GB'
 
# 4. Cprofile + snakeviz для общей картины
import cProfile, pstats
cProfile.run('main()', 'out.prof')
pstats.Stats('out.prof').sort_stats('cumulative').print_stats(20)

Типичные находки

  • apply(axis=1) в горячем цикле → np.where/векторизация.
  • merge без индекса/sort → выставить index, использовать validate=.
  • Чтение CSV без dtype → читать с dtype=/usecols=.
  • Большие object-колонки → перевод в category (df['c'] = df['c'].astype('category')).
  • Цикл по df.iterrows() → переписать в groupby.

Оптимизация типа

def reduce_mem(df):
    for c in df.select_dtypes('integer'):
        df[c] = pd.to_numeric(df[c], downcast='integer')
    for c in df.select_dtypes('float'):
        df[c] = pd.to_numeric(df[c], downcast='float')
    for c in df.select_dtypes('object'):
        if df[c].nunique() / len(df) < 0.5:
            df[c] = df[c].astype('category')
    return df

Подводные камни

  1. Без измерения 80% «оптимизаций» бесполезны. Сначала %lprun, потом код.
  2. df.memory_usage() без deep=True врёт по object-колонкам (показывает только указатели).
  3. category ускоряет groupby и экономит память, но ломается при наивном merge с другим dtype.
  4. Конвейерные операции (a.b.c.d) создают копии — иногда быстрее одна большая операция или inplace=True.
  5. Skewed groupby (один ключ — 90% данных) даже Dask делает медленным; решение — pre-split.

Эталонный ответ

Сначала измерить %lprun и memory_usage(deep=True), найти ту самую медленную строку, и переписать конкретно её — обычно apply→np.where, downcast типов, перевод в category.

Хочешь увидеть разбор?

Зарегистрируйся бесплатно — откроется развёрнутое решение этой задачи и ещё 4 на выбор.

Зарегистрироваться и увидеть разбор
Уже есть аккаунт? Войти