[!NOTE] Автоматический перевод Эта статья была автоматически переведена с оригинальной английской версии.

Сравнение современных движков обработки данных: Polars, DataFusion, Daft, Ray Data, Pandas и Spark

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

Современные пайплайны также обрабатывают изображения, аудио и видео. В таких задачах декодирование CPU может приводить к перегрузке GPU инференс, в то время как процесс сборки мусора в JVM и механизм Global Interpreter Lock в Python ограничивают throughput. Новые движки используют Rust и Apache Arrow, чтобы снизить эти затраты.

Для сравнения вариантов я провёл тестирование на производительности Polars, DataFusion, Daft, Ray Data и Spark на двух реальных датасеты. Рейсы такси в Нью-Йорке представляют собой табличные данные; изображения из набора Food-101 — мультимодальные пайплайн.

Этот репозиторий engine-comparison-demo в нём содержится код. Запустите его на собственном оборудовании, поскольку производительность движка зависит от аппаратной части, датасет, и объёма работы.

Кратко: Начинайте с определения структуры задачи, а не с поиска идеального бенчмарк. Polars является отличным стандартным выбором для работы с локальными DataFrame пайплайны; DataFusion подходит командам, нуждающимся в движке запросов эмбеддинг; Ray Data и Daft предназначены для обработки смешанных CPU/GPU пайплайны; в то время как Spark остается надёжным решением для распределённых SQL-запросов и процессов ETL. Указанные в данном руководстве показатели ускорения являются результатами тестирования конкретных нагрузок, а не универсальными гарантиями. Перед принятием решения обязательно запустите сопутствующий бенчмарки на своих данных и оборудовании.


Типы рабочих нагрузок по обработке данных

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

Два мира данных: структурированные и мультимодальные

Структурированные или табличные данные подразумевают фильтрацию, агрегацию и объединение таблиц. Как правило, они ограничены размером CPU и часто помещаются в память. По данным Polars, примерно 90% запросов обрабатывают объём данных менее 1 ТБ., Таким образом, множество таких компонентов может работать на одной современной машине вместо целого кластера.

Мультимодальные AI системы обрабатывают изображения, аудио или видео. Инференс может запускаться на GPU во время процесса декодирования CPU, однако удалённый доступ к данным и ограничения по группировке задач снижают эффективность пайплайн. Этот коэффициент варьируется в зависимости от конкретной инстанции и используемых операций, поэтому необходимо отслеживать уровень использования ресурсов на всём протяжении обработки, а не определять размер GPU изолированно.

Более современные движки ориентированы на разные сегменты этой области. Реализация кода в нативном формате позволяет снизить накладные расходы, связанные с работой языка Python, интерфейсы, совместимые с форматом Arrow, упрощают обмен данными, а потоковая обработка позволяет ограничить использование памяти в системах датасеты объёмом, превышающим размер оперативной памяти. Однако ни один из этих преимуществ не гарантирован автоматически для всех операций или способов преобразования.


Часть 1: обработка на одном узле

Pandas предпочитает эгзистентный подход к программированию в памяти модель. Многие распространённые операции генерируют промежуточные данные, причём параллельная обработка не координируется с помощью оптимизатора запросов. Такой компромисс позволяет сохранять простоту кода для исследовательских целей, но при выполнении крупных аналитических задач он может привести к значительным затратам ресурсов. В Polars’s PDS-H бенчмарк При масштабирующем коэффициенте 10 библиотека Pandas заняла примерно 365 секунд на обработку всего набора данных, тогда как потоковый движок Polars справился с этой задачей за 3,89 секунды. Полученное примерно 94-кратное различие в скорости обусловлено конкретной бенчмарк и настройками; это не является универсальным коэффициентом преобразования данных из Pandas в Polars.

Выполнение с немедленной обработкой vs. отложенное выполнение

Polars для работы с локальными табличными данными пайплайны

Polars Это практический стандартный вариант использования в тех случаях, когда локальный DataFrame пайплайн становится слишком объёмным для работы в режиме немедленной обработки в однопроцессной среде. Благодаря режиму отложенной обработки API план выполнения запроса формируется до фактической экспекуции, что позволяет применять такие оптимизации, как передача предикатов на более ранних этапах и упрощение проекции данных. В результате движок может выполнять операторы параллельно, а при наличии поддержки — в виде потоковых пакетов.

Разница увеличивается по мере роста объёма данных. В этот момент коэффициент масштабирования 100 (~100 ГБ), Сервис потоковой обработки Polars завершил работу за 23,94 секунды по сравнению с 152,27 секундами у его собственного внутреннего движка, работающего в памяти — это примерно в 6 раз быстрее при обработке данных объёмом, превышающим размер оперативной памяти.

Из engine_comparison_examples.ipynb:

import polars as pl

# scan_parquet reads only the schema — no data loaded yet
q = (
    pl.scan_parquet("yellow_tripdata_2024-01.parquet")
    .filter(
        (pl.col("trip_distance") > 5.0)
        & (pl.col("total_amount") > 30.0)
    )
    .group_by("payment_type")
    .agg(
        pl.col("total_amount").mean().alias("avg_fare"),
        pl.col("trip_distance").mean().alias("avg_distance"),
        pl.len().alias("trip_count"),
    )
)

# The entire plan is optimized and executed here, in parallel
result = q.collect()

Ключевое отличие заключается в способе выполнения модель. В приведённом выше запросе с ленивой обработкой Polars позволяет перенести фильтрацию и выбор столбцов непосредственно в процесс сканирования формата Parquet. Это позволяет пропускать группы строк и избегать чтения ненужных столбцов. Типичный запрос Pandas с эгзистентной обработкой пайплайн не имеет целостного плана выполнения запроса для оптимизации, хотя тщательное использование фильтров Parquet, отобранных столбцов и альтернативных бэкенды может частично компенсировать этот недостаток.

DataFusion для встраиваемых движков запросов

В то время как Polars — это библиотека, которую используют напрямую, DataFusion это движок, на основе которого строятся другие движки. Он обеспечивает работу InfluxDB 3.0, GreptimeDB, и Apple’s ускоритель Comet Spark.

В ноябре 2024 года Результаты запуска ClickBench, опубликованные проектом DataFusion, DataFusion показал лучшие результаты среди протестированных конфигураций Parquet в одноузловом режиме. Команда Embucket позже опубликовала Запуск теста TPC-H с коэффициентом масштабирования 1000 на одном крупном узле. Эти результаты демонстрируют, до каких масштабов можно дойти при определённых условиях; они не подтверждают, что для каждой нагрузки объёмом 1 ТБ обязательно следует избегать использования кластера.

Из engine_comparison_examples.ipynb:

from datafusion import SessionContext

ctx = SessionContext()
ctx.register_parquet("taxi", "yellow_tripdata_2024-01.parquet")

# SQL executed directly against Parquet — no intermediate copies
df = ctx.sql("""
    SELECT payment_type,
           COUNT(*)           AS trip_count,
           AVG(trip_distance) AS avg_distance,
           AVG(total_amount)  AS avg_fare
    FROM taxi
    WHERE trip_distance > 5.0 AND total_amount > 30.0
    GROUP BY payment_type
    ORDER BY trip_count DESC
""")
result = df.to_pandas()

сила DataFusion заключается в его модульность. Расширение APIs предназначено для работы с пользовательскими каталогами, поставщиками таблиц, правилами оптимизатора и планами выполнения, поэтому оно используется каждый раз, когда кто-либо разрабатывает собственную платформу обработки данных или эмбеддинг движок запросов в рамках своего продукта.

Некорректность обработки мультимодальных данных

нелепость Включает изображения, аудио, видео и эмбеддинги в рабочий процесс DataFrame. Он предоставляет встроенные выражения для операций вроде декодирования изображений и загрузки контента по URL, благодаря чему типовые шаги предобработки не требуют использования циклов на уровне каждой строки в Python. Именно такая интеграция является основной причиной выбора этого инструмента вместо обычных табличных движков.

Из engine_comparison_examples.ipynb:

import daft

df = daft.read_parquet("yellow_tripdata_2024-01.parquet")

result = (
    df.where(
        (daft.col("trip_distance") > 5.0)
        & (daft.col("total_amount") > 30.0)
    )
    .groupby("payment_type")
    .agg(
        daft.col("total_amount").mean().alias("avg_fare"),
        daft.col("trip_distance").mean().alias("avg_distance"),
        daft.col("trip_distance").count().alias("trip_count"),
    )
    .collect()
)

API покажется знакомым тем, кто работал с Pandas, однако под капотом используется та же самая стек-архитектура Rust + Arrow, что и в Polars и DataFusion. Преимущества этого решения проявляются тогда, когда «строки» в вашем DataFrame представляют собой изображения, PDF-файлы или тензоры.

Одноузловая бенчмарк

Я запустил все четыре движка, а также нативный Rust с использованием Polars-rs, на объёме данных около 41 млн. Поездки жёлтых такси в Нью-Йорке за весь 2024 год. Полные результаты приведены в репозиторий демо-версии:

Бенчмарк Результаты: производительность однодольного узла

При таком размере данных (около 660 МБ в формате Parquet) все четыре более новых движка обрабатывают их довольно быстро. Указанный выше коэффициент преобразования Polars в Pandas — 94 к 1 — был получен с использованием PDS-H; при работе с набором данных о такси, содержащим 41 миллион строк, разница оказалась меньше. Polars, DataFusion, Daft и Polars-rs показали схожие результаты, причём DataFusion зафиксировал наименьшее общее время выполнения в данном тесте. Этот показатель полезен для оценки данного запроса, но не может считаться стабильным рейтингом: для окончательного выбора между столь близкими по производительности движками необходимо провести адаптацию API и повторные измерения на репрезентативных данных.


Часть 2: мультимодальные данные

В процессах TEL в табличном формате обычно происходит сокращение объёма данных: выполняются фильтрация и агрегация, в результате чего записывается меньше информации, чем было прочитано. Мультимодальные AI пайплайны действуют наоборот: один путь к документу может привести к появлению десятков текстовых чанки и эмбеддинг векторов.

В традиционных реализациях PySpark пайплайн изображения и аудио обычно поступают в виде двоичных данных и передаются в библиотеки на Python для декодирования или преобразования. Пересечение границы JVM/Python и сериализация полученных результатов могут составлять значительную часть общего времени выполнения задачи. Spark позволяет использовать пути, основанные на формате Arrow, векторизованные UDF и интеграции с ускорителями, однако принятие таких решений требует тщательного проектирования и анализа эффективности.

Выполнение в формате конвейера и использование GPU

Spark планирует обработку данных по этапам, разделенным границами шаффлинга. В простой реализации загрузка данных, их декодирование и инференс могут выполняться последовательно, в результате чего остается незагруженной либо CPU, либо GPU мощности. Благодаря поддержке Spark функций планирования ресурсов GPU и специальных плагинов простой простой состояние не является неизбежным; ключевой момент заключается в том, что перекрытие задач и эффект обратного давления не являются автоматическими свойствами обычной задачи на PySpark DataFrame.

Выполнение с использованием конвейера Модель

Выполнение с использованием конвейера представляет собой альтернативный подход. Вместо последовательной обработки этапов движок параллелизует операции ввода-вывода, CPU и GPU инференс, благодаря чему в любой момент времени только медленный компонент остается в состоянии ожидания.

Обработка изображений бенчмарк

Я провёл тестирование производительности обработки изображений на 500 реальных фотографиях из Основы работы с данными о пище датасет. результаты выполнения репозиторий демо-версии:

Бенчмарк Результаты: производительность в мультимодальных сценариях

Polars и DataFusion отсутствуют в данном тесте, поскольку он ориентирован на работу с нативными изображениями, а не на табличные выражения. В ходе тестирования с 500 изображениями реализация Daft показала скорость обработки на 3,6 раза выше, чем базовый вариант с Pandas + Pillow, в то время как реализация на Rust была в 4,2 раза быстрее. Этот пример полезен для демонстрации накладных расходов, связанных с оркестрация, однако его масштаб недостаточен для самостоятельной оценки производительности в реальных условиях throughput.


Часть 3: распределённая обработка

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

Apache Spark

Spark представляет собой зрелый инструмент для крупномасштабной обработки табличных данных в рамках процессов ETL, выполнения объёмных операций соединения данных с использованием механизма shuffle, а также подходит для организаций, уже использующих его экосистему. Возможности восстановления данных на основе истории изменений и широкая поддержка различных платформ становятся ключевыми преимуществами в тех случаях, когда надёжность и удобство работы превышают значение локальной скорости обработки. Сочетание CPU/GPU пайплайны требует более тщательной настройки ресурсов и разработки пайплайн по сравнению с подходом, основанным на SQL, где Ray Data и Daft предлагают более целенаправленную абстракцию.

Из distributed_spark.ipynb:

from pyspark.sql import SparkSession
from pyspark.sql import functions as F

spark = SparkSession.builder \
    .appName("TaxiETL") \
    .config("spark.sql.adaptive.enabled", "true") \
    .getOrCreate()

orders = spark.read.parquet("s3a://lake/taxi/*.parquet")
zones = spark.read.csv("s3a://lake/taxi_zones.csv", header=True)

# Spark excels at this: joining massive tables with a shuffle
result = (
    orders
    .filter(F.col("trip_distance") > 5.0)
    .join(zones, orders.PULocationID == zones.LocationID, "inner")
    .groupBy("Borough")
    .agg(
        F.sum("total_amount").alias("total_revenue"),
        F.avg("total_amount").alias("avg_fare"),
    )
    .orderBy(F.desc("total_revenue"))
)

Ray Data для гетерогенных вычислений

Данные Ray был разработан с учётом нагрузок типа AI. Вместо барьеров этапов в Spark он использует стриминг модель это обеспечивает питание GPUs. Основой для существования данной функции является смешанное планирование ресурсов: можно указать, что один агент требует «1 GPU, 4 CPUs», а другой — только CPUs, после чего Ray сам определяет оставшуюся часть.

Amazon сообщил(а) более 120 миллионов долларов ежегодной экономии После переноса отобранных внутренних рабочих нагрузок по обработке данных с Spark на Ray в ходе исследования была зафиксирована в 91% лучшая эффективность затрат в рамках пилотного проекта, а в режиме работы — в 82% по объёму в 1 ГиБ входных данных из S3. Размеры улучшений впечатляющие, однако речь идёт о кейс-стади об миграции конкретных нагрузок, а не о прогнозируемой экономии при типичном внедрении Ray.

Из distributed_ray.ipynb:

import ray

ray.init()

ds = ray.data.read_images("s3://my-bucket/food101/")

class ImageClassifier:
    def __init__(self):
        import torch
        from torchvision.models import resnet18, ResNet18_Weights
        self.model = resnet18(weights=ResNet18_Weights.DEFAULT).cuda()
        self.model.eval()
        self.preprocess = ResNet18_Weights.DEFAULT.transforms()

    def __call__(self, batch):
        import torch
        tensors = torch.stack([
            self.preprocess(img) for img in batch["image"]
        ]).cuda()
        with torch.no_grad():
            preds = self.model(tensors)
        return {
            "prediction": preds.argmax(dim=1).cpu().numpy(),
            "confidence": preds.max(dim=1).values.cpu().numpy(),
        }

# ActorPoolStrategy creates persistent GPU workers
predictions = ds.map_batches(
    ImageClassifier,
    compute=ray.data.ActorPoolStrategy(size=4),
    num_gpus=1,
    batch_size=64,
)
predictions.write_parquet("s3://output/predictions/")

Один важный момент, который стоит учитывать: по мере увеличения количества CPUs внутри GPU, производительность Ray Data растёт, тогда как у других движков она достигает плато. В бенчмарки в Anyscale, Переход от соотношения 4:1 к соотношению 32:1 CPU–GPU привёл к ускорению обработки изображений инференс в 3 раза, поскольку подачи данных CPU наконец смогли полностью сопровождать темпы работы GPU.

Распределённый Daft

масштабирование по горизонтали с помощью своих двигатель флотилии: На каждом узле работает по одному рабочему процессу Swordfish, в то время как слой Flotilla отвечает за планирование на уровне всего кластера. Swordfish обеспечивает локальную экспекуцию кода на языке Rust и обработку ввода-вывода пайплайны с использованием технологии потоковой обработки небольших пакетов данных, благодаря чему каждый узел остается загруженным и не вынужден ожидать результатов следующей стадии.

В бенчмарки, опубликовано компанией Daft, В четырёх мультимодальных нагрузках флотила работала в 2–18 раз быстрее тестированных реализаций Spark. Наибольшая разница была зафиксирована при задаче обнаружения объектов в видео. Считайте эти результаты доказательством того, что архитектура выполнения играет ключевую роль для этих пайплайны, после чего сравните их с результатами конкурирующей платформы Anyscale, приведёнными ниже, а также с результатами локальных тестов.

Anyscale, компания‑разработчик Ray, опубликовала конкурирующий бенчмарк в ходе которой Ray Data сократил или устранил разрыв по показателям производительности на высокоинтенсивных инстансах CPU после настройки параметров. В совокупности эти два исследования провайдеров служат основанием для тестирования типичных операторов и соотношений CPU к GPU, а не для формирования окончательного рейтинга.

Из distributed_daft.ipynb:

import daft
from daft import col

# Daft's Flotilla engine distributes work across the cluster
df = daft.read_parquet("s3://data-lake/pdf_metadata/*.parquet")

# Download PDFs — Daft parallelizes downloads in Rust
df = df.with_column("pdf_bytes", col("pdf_url").url.download())

# Define a GPU UDF for embedding generation
@daft.udf(return_dtype=daft.DataType.list(daft.DataType.float32()))
class TextEmbedder:
    def __init__(self):
        from sentence_transformers import SentenceTransformer
        self.model = SentenceTransformer("all-MiniLM-L6-v2", device="cuda")

    def __call__(self, text_col):
        texts = text_col.to_pylist()
        embeddings = self.model.encode(texts, batch_size=32)
        return [emb.tolist() for emb in embeddings]

# Daft schedules CPU downloads and GPU embeddings simultaneously
df = df.with_column("embedding", TextEmbedder(col("text")))
df.write_parquet("s3://output/embeddings/")

Распределённое сравнение

ФункциональностьSparkДанные лучейDaft (Флотилия)
Выполнение МодельРаспределение задач по ядрам с использованием разделения памятиЗадачи потоковой обработки и актерыОдин меч-рыба на узел, потоковые пакеты
ПреимуществаМасштабные процессы обработки SQL/ETL и механизмы обеспечения отказоустойчивостиГетерогенные вычисления, насыщение GPUМультимодальная пайплайн-архитектура с ограниченной памятью
GPU — механизмы управленияПланирование ресурсов и плагины экосистемыЯвные ресурсы задачи и исполнителяИнтегрированное планирование CPU/GPU пайплайн
Типичная настройкаЭкзекьюторы — память, разделы и ускорителиПакеты обработки, актеры, хранилище объектовПакеты обработки, ресурсы, конкурентность ввода/вывода
Хороший пример оценкиКрупные задачи на SQL/ETL с интенсивным использованием операций перемешивания данныхОбучение инференс с использованием смешанных ресурсовМультимодальный ввод и преобразование данных

Часть 4: роль Rust и Arrow

Под внешней конкуренцией гораздо более интересной остается тенденция к сближению подходов. Фреймворки Polars, Daft и DataFusion активно используют язык Rust, тогда как Ray сочетает в себе нативные компоненты с возможностями работы на Python APIs. Все эти решения способны обмениваться данными через соответствующие механизмы. Apache Arrow экосистема.

Конвергенция Rust и Arrow

Этот Интерфейс Arrow PyCapsule (__arrow_c_stream__ Протокол предоставляет совместимым библиотекам стандартизированный способ обмена потоками Arrow. Благодаря ему можно избежать сериализации по строкам, а также повторно использовать буферы в тех случаях, когда схемы и структуры памяти совместимы. Однако процессы материализации, перегруппировки данных, преобразования типов или передачи между устройствами всё равно могут влечь за собой копирование информации; поэтому необходимо проверять эффективность такой передачи с помощью инструментов профилирования, а не полагаться на то, что она происходит без затрат.

from datafusion import SessionContext
import polars as pl

# Compute in DataFusion (Rust-native execution)
ctx = SessionContext()
ctx.register_parquet("events", "events.parquet")
df = ctx.sql("""
    SELECT user_id, COUNT(*) as event_count
    FROM events WHERE event_type = 'purchase'
    GROUP BY user_id HAVING COUNT(*) > 5
""")

# Convert through Arrow; compatible buffers may be reused
arrow_table = df.to_arrow_table()
df_polars = pl.from_arrow(arrow_table)

# Continue analysis in Polars
result = df_polars.with_columns(
    pl.col("event_count").rank().alias("rank")
).sort("rank")

Почему в новых движках используется Rust для нативной реализации?

  1. В коде на Rust отсутствует трейсинг коллектор мусора. Механизм владения позволяет встроенным операторам более прямо управлять процессами выделения и освобождения памяти. Это способно снизить количество латентность, связанных с работой коллектора, однако оно не устраняет проблемы давления на память или сбоев из-за её недостатка.

  2. Компактные нативные представления. Структуры в Rust не содержат заголовков объектов Java. Практическая польза от этого зависит от архитектуры движка: в колоночных системах JVM также избегается представление каждого значения в виде отдельного объекта.

  3. Более строгая проверка конкурентности. Синтаксис Rust с поддержкой безопасного программирования позволяет исключить множество ситуаций конкурентных проблем на этапе компиляции. В коде движка всё ещё могут встречаться блоки с небезопасным использованием ресурсов и логические ошибки конкурентности, однако сам язык сужает спектр возможных сбоев.

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


Часть 5: использование нескольких движков

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

Коэкзистенция Модель: мультиинжиниринг Пайплайн

Один из возможных вариантов промежуточной обработки предусматривает использование Spark для выполнения стандартных операций объединения данных в лейкхаусах, запись результата в формат открытой таблицы или файла, а затем передачу полученного результата в Ray Data или Daft с целью проведения GPU инференс или мультимодальных преобразований. Polars или DuckDB позволяют выполнять локальный анализ непосредственно на этих же файлах. Команда меньшего размера может вполне обойтись одним из этих движков; добавление второго становится оправданным тогда, когда измеримый боттлнек требует расширения рамок их эксплуатации.

То, что делает такую структуру возможной, — это открытые форматы: Parquet, Delta, Iceberg, Arrow. Каждый компонент этой стек-архитектуры поддерживает чтение и запись в этих форматах нативно, поэтому передача данных между этапами осуществляется просто через указание пути к файлу.

Выбор движка на основе кейса оценки

Пример оценкиСписок кандидатовПроверка корректности
Локальные аналитические DataFrameыPolars / DuckDBAPI соотношение объёма памяти, вычислительных ресурсов и сложности запросов
Существующие распределённые решения для SQL/ETLApache SparkПоведение смешивания, операции, стоимость
Параллельные вычисления нативно на PythonПотери производительности из-за задач-plannerа, сбои модель
Смешанная подготовка с использованием CPU/GPU или инференсRay Data / DaftИспользование ускорителя, обратное давление
Мультимодальный ввод данныхDaft / Ray DataОператоры нативного уровня, поведение при попытках повтора
Встроенный движок запросовDataFusionРасширение APIs, покрытие SQL-запросов

Диагональное масштабирование

Долгое время существовало два основных варианта решения: масштабирование «вверх» (использование более мощной машины) или масштабирование «в стороны» (добавление дополнительных машин). Polars Cloud в этом случае применяется так называемое diagonal scaling: сначала происходит горизонтальное масштабирование при чтении данных из облачного хранилища для максимизации объёма ввода-вывода, а затем, после того как фильтрация и агрегация сокращают объём данных, система сворачивается до одного крупного узла, полностью опуская этап распределённой перестановки данных.

Основной урок заключается в сравнении стоимости выполнения одного успешного задания, а не цены за час работы инстанса. Более мощный узел может оказаться дешевле, если экономия, обусловленная рантайм, превышает его более высокую стоимость, однако итоговый результат зависит от объёма ввода/вывода, нагрузки на память и количества работы, которая удаётся выполнить до начала процедуры перетасовки.


Воспроизвести сравнение

Этот репозиторий демо-примеров для сопровождения В этом репозитории хранятся скрипты, ноутбуки и среда, используемые для проведения сравнений. Приведённая ниже простая команда обрабатывает данные о такси за один месяц; чтобы воссоздать более объёмный анализ за весь год, показанный ранее, следуйте инструкциям из репозитория датасет.

# Install dependencies
uv sync

# Run the tabular benchmark (~2.9M NYC taxi trips)
uv run python -m engine_comparison.benchmarks.tabular

# Run the multimodal benchmark (500 real food photos)
uv run python -m engine_comparison.benchmarks.multimodal

# Run native Rust benchmarks (Polars-rs + image crate)
cd rust_benchmark && cargo run --release && cd ..

В этом репозитории также представлены ноутбуки для параллельных сравнений API, а также конфигурации Docker Compose для локальной распределённой работы Spark, Ray и Daft.


Основные выводы

  1. Докажите, что одной машины недостаточно. Результаты тестов масштабирования показывают, что некоторые объёмные аналитические сканирования помещаются на одном узле. Перед тем как соглашаться на дополнительные нагрузки кластера, необходимо измерить потребности в памяти, операциях ввода/вывода, объёмах данных, выходящих за пределы оперативной памяти, и процессах восстановления.

  2. Способ выполнения модели имеет гораздо большее значение, чем знакомая синтаксис. Такие концепции, как ленивые планирование операторы, метод пушдаун, потоковая обработка и параллельные операции, в значительной степени объясняют разницу между скриптами на основе DataFrame с ранним выполнением и аналитическими движками.

  3. Измеряйте весь акселератор пайплайн. Процессы декодирования, сетевого ввода/вывода, группировки операций, сериализации и реализации механизма обратного давления определяют, будет ли GPU находиться в состоянии активной работы. Инструменты Ray Data и Daft позволяют выделить этот набор аспектов в отдельную центральную абстракцию; Spark может обеспечить поддержку такого подхода за счёт дополнительных архитектурных решений и инструментов.

  4. Сначала отсортируйте кандидатов по уровню нагрузки, затем бенчмарк. Используйте степень соответствия экосистеме для сужения списка возможных вариантов, а также типичную задачу «от начала до конца», чтобы сделать окончательный выбор. Результаты оценки поставщиков с помощью бенчмарк представляют собой лишь гипотезы, а не гарантии.

  5. Открытые форматы обеспечивают возможность обратного перехода. Интерфейсы, совместимые с форматом стрелок, формат Parquet и открытые форматы таблиц позволяют снизить затраты на передачу данных. Необходимо уточнить, переиспользуются ли буферы при конкретной конвертации или они копируются заново.

Если вам нужна отправная точка, рассмотрите Polars для работы с локальными табличными данными пайплайн, а также Daft или Ray Data для обработки смешанных CPU/GPU пайплайн структур. Используйте Spark в тех случаях, когда его возможности дистрибутированной обработки, существующая экосистема или инфраструктура соответствуют вашим требованиям, а не просто когда объем данных датасет превышает определённый порог.


Список литературы

Бенчмарки и данные о производительности

Движки и фреймворки

Архитектура и экосистема

Репозиторий демонстрации

Датасеты