Learning Platform
Глоссарий Troubleshooting
Урок 05.05 · 12 мин
Средний
BenchmarkPerformance ComparisonBest Practices

Сравнение производительности: benchmark UDF-стратегий

Benchmark: 100 миллионов строк, операция upper()

Мы протестировали все четыре подхода на одном датасете (100M строк, операция upper() на строковом столбце) на кластере с 4 executors × 4 cores:

Built-in
1.0x — baseline (8 сек)
Scala UDF
~2x slower (16 сек)
Pandas UDF
~5x (40 сек)
Python UDF
~100x (13 мин)
TIP

Как читать график: длина бара показывает относительную скорость, а не абсолютное время. Built-in (emerald) — самый быстрый, Python UDF (red) — самый медленный. Обратите внимание на масштаб: Python UDF в 100 раз медленнее built-in, поэтому его бар практически невидим.

Почему такая разница?

ПодходOverhead источникExecution modelCatalystCodeGen
Built-inНетJVM Tungsten nativeПолная оптимизацияWhole-Stage CodeGen
Scala UDFBoxing/unboxing типовJVM native callЧёрный ящикНет
Pandas UDFArrow batch transferJVM → Arrow → Python → Arrow → JVMЧёрный ящикНет
Python UDFPer-row serializationJVM → pickle → socket → Python → socket → JVMЧёрный ящикНет

Ключевой вывод: overhead растёт с каждым уровнем абстракции. Built-in работает “внутри” JVM. Scala UDF — “рядом” с JVM. Pandas UDF передаёт батчи через Arrow. Python UDF передаёт каждую строку через socket.

Benchmark: сложные операции

Для более сложных операций (regex matching, математические вычисления, ML-инференс) разрыв сокращается, потому что доля полезной работы растёт:

Regex extraction + string manipulation (100M строк):

Built-in
1.0x (25 сек)
Scala UDF
~1.7x (42 сек)
Pandas UDF
~3x (75 сек)
Python UDF
~30x (12.5 мин)

Для сложных операций Python UDF “всего” в ~30x медленнее (вместо 100x), а Pandas UDF — в ~3x. Это потому, что Python-код занимает большую долю общего времени.

Матрица принятия решений

Тип операцииРекомендацияПочему
Строковые операции (upper, trim, replace)Built-inupper(), trim(), regexp_replace()
Условная логика (if/else)Built-inwhen().otherwise(), case в SQL
Работа с датами (разница, форматирование)Built-indatediff(), date_format(), months_between()
Математические формулыBuilt-inpow(), sqrt(), log(), арифметические операции
Работа с массивами/mapBuilt-inarray_contains(), explode(), map_keys()
Кастомная валидация (ИНН, ОГРН)Scala UDFСложная логика без Python-зависимостей
Интеграция с Java-библиотекойScala UDFПрямой доступ к JVM
ML-инференс (scikit-learn, PyTorch)Pandas UDF (Iterator)Загрузка модели один раз + vectorized predict
Сложные pandas-операции над группамиPandas UDF (Grouped Map)applyInPandas() для полного контроля
NumPy-вычисления (FFT, линейная алгебра)Pandas UDF (Scalar)Vectorized C-операции
Единственная оставшаяся опцияPython UDFКогда ничего другого не подходит

Чек-лист перед созданием UDF

Прежде чем писать UDF, пройдите этот чек-лист:

  1. Проверьте встроенные функции: Spark SQL Functions — 300+ функций
  2. Попробуйте when/otherwise: большинство условной логики выражается через них
  3. Рассмотрите higher-order functions: transform(), filter(), aggregate() для работы с массивами
  4. Рассмотрите SQL expression: иногда SQL CASE WHEN проще, чем цепочка when().otherwise()
  5. Если UDF неизбежен: Scala UDF > Pandas UDF > Python UDF

Анти-паттерн: “мы Python-команда, поэтому используем Python UDF”

# Типичное обоснование: "наша команда не знает Scala,
# и нам проще писать Python UDF"

# ПРОБЛЕМА: Python UDF для простой конкатенации
@udf(returnType=StringType())
def full_name(first, last):
    return f"{first} {last}" if first and last else None

# Результат: 100x overhead на каждую строку

# РЕШЕНИЕ: встроенная concat()
from pyspark.sql.functions import concat_ws
df.withColumn("full_name", concat_ws(" ", col("first_name"), col("last_name")))

“Мы Python-команда” — не повод использовать Python UDF. Встроенные функции вызываются из PySpark точно так же, как из Scala. Разница не в языке вызова, а в том, где выполняется логика — на JVM (встроенная) или в Python-процессе (UDF).

Проверка знанийKnowledge check
Почему разрыв в производительности между Python UDF и встроенными функциями сокращается для сложных операций (с ~100x до ~30x)?
ОтветAnswer
Для простых операций (upper, trim) полезная работа занимает ~5ns, а overhead сериализации ~5μs, поэтому overhead доминирует (1000x). Для сложных операций (regex, математика) полезная работа занимает ~100μs, а overhead остаётся ~5μs, поэтому доля overhead в общем времени падает. Формула: total = overhead + work. Для простой операции: 5μs + 5ns ≈ 5μs (overhead = 99.9%). Для сложной: 5μs + 100μs ≈ 105μs (overhead = 5%). Чем сложнее UDF, тем меньше относительный impact сериализации.
Проверка знанийKnowledge check
Компания обрабатывает 500M строк ежедневно. Data engineer предлагает Pandas UDF для вычисления кастомной метрики. Какие вопросы нужно задать перед принятием решения?
ОтветAnswer
1) Можно ли выразить метрику через комбинацию встроенных функций? (Проверить 300+ функций Spark SQL). 2) Если нет -- нужна ли Python-библиотека (pandas, numpy, scikit-learn)? Если да -- Pandas UDF оправдан. Если нет -- Scala UDF даст ~2.5x ускорение (5x vs 2x от built-in). 3) Какой тип Pandas UDF подходит: Scalar (элементная), Iterator (ML-модель), Grouped Map (группа как DataFrame)? 4) Каков размер батча (maxRecordsPerBatch) и хватит ли памяти Python worker?

Резюме модуля

Иерархия производительности UDF в Spark:

1x    Built-in (Catalyst + CodeGen + JVM)  → ВСЕГДА ПЕРВЫЙ ВЫБОР
~2x   Scala UDF (JVM, нет CodeGen)         → Сложная логика без Python
~5x   Pandas UDF (Arrow батчи)             → ML, pandas, NumPy
~100x Python UDF (per-row сериализация)    → ПОСЛЕДНИЙ ВАРИАНТ

Подробнее о каждом подходе — в предыдущих уроках модуля (cross-ref: 01-builtin-vs-udfs, 02-python-udf-overhead, 03-pandas-udfs-arrow, 04-scala-udfs).

Что дальше?

В следующем модуле мы переходим к оптимизации хранения данных — форматы файлов (Parquet, ORC, Avro), Z-ordering, bloom filters и решение проблемы маленьких файлов.

Проверьте понимание

Результат: 0 из 0
Аналитический
Вопрос 1 из 6. Benchmark: upper() на 100M строк. Built-in: 8 сек, Scala UDF: 16 сек, Pandas UDF: 40 сек, Python UDF: 13 мин. Почему для сложных операций (regex) разрыв сокращается?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 5