Перейти к основному содержимому
Перейти к основному содержимому

Руководство по производительности

DataStore обеспечивает значительный прирост производительности по сравнению с pandas для многих операций. В этом руководстве объясняется, почему это так и как оптимизировать ваши рабочие нагрузки.

Почему DataStore быстрее

1. SQL Pushdown

Операции выполняются непосредственно в источнике данных:

# pandas: Loads ALL data, then filters in memory
df = pd.read_csv("huge.csv")       # Load 10GB
df = df[df['year'] == 2024]        # Filter in Python

# DataStore: Filter at source
ds = pd.read_csv("huge.csv")       # Just metadata
ds = ds[ds['year'] == 2024]        # Filter in SQL
df = ds.to_df()                    # Only load filtered data

2. Отсечение столбцов

Читаются только необходимые столбцы:

# DataStore: Only reads name, age columns
ds = pd.read_parquet("wide_table.parquet")
result = ds.select('name', 'age').to_df()

# vs pandas: Reads all 100 columns, then selects

3. Ленивое вычисление

Несколько операций объединяются в один запрос:

# DataStore: One optimized SQL query
result = (ds
    .filter(ds['amount'] > 100)
    .groupby('region')
    .agg({'amount': 'sum'})
    .sort('sum', ascending=False)
    .head(10)
    .to_df()
)

# Becomes:
# SELECT region, SUM(amount) FROM data
# WHERE amount > 100
# GROUP BY region ORDER BY sum DESC LIMIT 10

Бенчмарк: DataStore и pandas

Тестовая среда

  • Данные: 10 миллионов строк
  • Оборудование: стандартный ноутбук
  • Формат файла: CSV

Результаты

Операцияpandas (мс)DataStore (мс)Победитель
GroupBy count34717DataStore (19.93x)
Combined ops1,535234DataStore (6.56x)
Complex pipeline2,047380DataStore (5.39x)
MultiFilter+Sort+Head1,963366DataStore (5.36x)
Filter+Sort+Head1,537350DataStore (4.40x)
Head/Limit16645DataStore (3.69x)
Ultra-complex (10+ ops)1,070338DataStore (3.17x)
GroupBy agg406141DataStore (2.88x)
Select+Filter+Sort1,217443DataStore (2.75x)
Filter+GroupBy+Sort466184DataStore (2.53x)
Filter+Select+Sort1,285533DataStore (2.41x)
Sort (single)1,7421,197DataStore (1.45x)
Filter (single)276526Сравнимо
Sort (multiple)9471,477Сравнимо

Ключевые выводы

  1. Операции GroupBy: DataStore до 19.93 раза быстрее
  2. Сложные пайплайны обработки: DataStore в 5–6 раз быстрее (благодаря SQL pushdown)
  3. Простые операции выборки (slice): Производительность сопоставима — разница пренебрежимо мала
  4. Оптимальный сценарий: Многошаговые операции с groupby/агрегацией
  5. Zero-copy: to_df() не добавляет накладных расходов на преобразование данных

Когда выигрывает DataStore

Тяжёлые агрегации

# DataStore excels: 19.93x faster
result = ds.groupby('category')['amount'].sum()

Сложные конвейеры обработки данных

# DataStore excels: 5-6x faster
result = (ds
    .filter(ds['date'] >= '2024-01-01')
    .filter(ds['amount'] > 100)
    .groupby('region')
    .agg({'amount': ['sum', 'mean', 'count']})
    .sort('sum', ascending=False)
    .head(20)
)

Обработка больших файлов

# DataStore: Only loads what you need
ds = pd.read_parquet("huge_file.parquet")
result = ds.filter(ds['id'] == 12345).to_df()  # Fast!

Операции над несколькими столбцами

# DataStore: Combines into single SQL
ds['total'] = ds['price'] * ds['quantity']
ds['is_large'] = ds['total'] > 1000
ds = ds.filter(ds['is_large'])

Когда pandas сопоставим по производительности

В большинстве сценариев DataStore соответствует производительности pandas или превосходит её. Однако в следующих конкретных случаях pandas может быть немного быстрее:

Небольшие наборы данных (<1,000 строк)

# For very small datasets, overhead is minimal for both
# Performance difference is negligible
small_df = pd.DataFrame({'x': range(100)})

Простые операции среза

# Single slice operations without aggregation
df = df[df['x'] > 10]  # pandas slightly faster
ds = ds[ds['x'] > 10]  # DataStore comparable

Пользовательские лямбда-функции на Python

# pandas required for custom Python code
def complex_function(row):
    return custom_logic(row)

df['result'] = df.apply(complex_function, axis=1)
Важно

Даже в сценариях, когда DataStore оказывается «медленнее», производительность обычно сопоставима с pandas — разница пренебрежимо мала для практического использования. Преимущества DataStore в сложных операциях значительно перевешивают эти редкие случаи.

Для тонкой настройки выполнения см. раздел Execution Engine Configuration.


Zero-copy интеграция DataFrame

DataStore использует zero-copy при чтении и записи pandas DataFrame. Это означает:

# to_df() does NOT copy data - it's a zero-copy operation
result = ds.filter(ds['x'] > 10).to_df()  # No data conversion overhead

# Same for creating DataStore from DataFrame
ds = DataStore(existing_df)  # No data copy

Ключевые выводы:

  • to_df() практически не имеет накладных расходов — нет сериализации и копирования памяти
  • Создание DataStore из pandas DataFrame происходит мгновенно
  • Память разделяется между DataStore и представлениями pandas

Рекомендации по оптимизации

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

Для нагрузок с интенсивным использованием агрегаций, где вам не нужен точный формат вывода pandas (порядок строк, столбцы MultiIndex, корректировки dtype), включите режим повышенной производительности для максимальной пропускной способности:

from chdb.datastore.config import config

config.use_performance_mode()

# Now all operations use SQL-first execution with no pandas overhead:
# - Parallel Parquet reading (no preserve_order)
# - Single-SQL aggregation (filter+groupby in one query)
# - No row-order preservation overhead
# - No MultiIndex, no dtype corrections
result = (ds
    .filter(ds['amount'] > 100)
    .groupby('region')
    .agg({'amount': ['sum', 'mean', 'count']})
)

Ожидаемый прирост производительности: Ускорение выполнения рабочих нагрузок с фильтрацией и groupby в 2–8 раз, снижение потребления памяти при работе с крупными файлами Parquet.

Подробности см. в разделе Performance Mode.

2. Используйте формат Parquet вместо CSV

# CSV: Slower, reads entire file
ds = pd.read_csv("data.csv")

# Parquet: Faster, columnar, compressed
ds = pd.read_parquet("data.parquet")

# Convert once, benefit forever
df = pd.read_csv("data.csv")
df.to_parquet("data.parquet")

Ожидаемый прирост производительности: операции чтения в 3–10 раз быстрее

3. Выполняйте фильтрацию как можно раньше

# Good: Filter first, then aggregate
result = (ds
    .filter(ds['date'] >= '2024-01-01')  # Reduce data early
    .groupby('category')['amount'].sum()
)

# Less optimal: Process all data
result = (ds
    .groupby('category')['amount'].sum()
    .filter(ds['sum'] > 1000)  # Filter too late
)

4. Выбирайте только нужные столбцы

# Good: Column pruning
result = ds.select('name', 'amount').filter(ds['amount'] > 100)

# Less optimal: All columns loaded
result = ds.filter(ds['amount'] > 100)  # Loads all columns

5. Используйте агрегатные функции SQL

# GroupBy is where DataStore shines
# Up to 20x speedup!
result = ds.groupby('category').agg({
    'amount': ['sum', 'mean', 'count', 'max'],
    'quantity': 'sum'
})

6. Используйте head() вместо выполнения полных запросов

# Don't load entire result if you only need a sample
result = ds.filter(ds['type'] == 'A').head(100)  # LIMIT 100

# Avoid this for large results
# result = ds.filter(ds['type'] == 'A').to_df()  # Loads everything

7. Пакетные операции

# Good: Single execution
result = ds.filter(ds['x'] > 10).filter(ds['y'] < 100).to_df()

# Bad: Multiple executions
result1 = ds.filter(ds['x'] > 10).to_df()  # Execute
result2 = result1[result1['y'] < 100]       # Execute again

8. Используйте explain() для оптимизации

# View the query plan before executing
query = ds.filter(...).groupby(...).agg(...)
query.explain()  # Check if operations are pushed down

# Then execute
result = query.to_df()

Профилирование нагрузки

Включите профилирование

from chdb.datastore.config import config, get_profiler

config.enable_profiling()

# Run your workload
result = your_pipeline()

# View report
profiler = get_profiler()
profiler.report()

Определение узких мест

Performance Report
==================
Step                    Duration    % Total
----                    --------    -------
SQL execution           2.5s        62.5%     <- Bottleneck!
read_csv                1.2s        30.0%
Other                   0.3s        7.5%

Сравнение подходов

# Test approach 1
profiler.reset()
result1 = approach1()
time1 = profiler.get_steps()[-1]['duration_ms']

# Test approach 2
profiler.reset()
result2 = approach2()
time2 = profiler.get_steps()[-1]['duration_ms']

print(f"Approach 1: {time1:.0f}ms")
print(f"Approach 2: {time2:.0f}ms")

Краткое резюме рекомендаций

РекомендацияРезультат
Включите режим повышенной производительностиВ 2–8 раз быстрее для нагрузок с агрегациями
Используйте файлы ParquetЧтение в 3–10 раз быстрее
Фильтруйте как можно раньшеСократите объём обрабатываемых данных
Выбирайте только нужные столбцыСократите I/O и потребление памяти
Используйте GroupBy/агрегацииДо 20 раз быстрее
Объединяйте операции в батчиИзбегайте повторного выполнения
Проводите профилирование перед оптимизациейНайдите реальные узкие места
Используйте explain()Проверьте оптимизацию запроса
Используйте head() для выборокИзбегайте полного сканирования таблицы

Краткое руководство по выбору

Ваша рабочая нагрузкаРекомендация
GroupBy/агрегацияИспользуйте DataStore
Сложный многошаговый конвейер обработки данныхИспользуйте DataStore
Крупные файлы с фильтрамиИспользуйте DataStore
Простые операции выборки (slice)Любой вариант (сопоставимая производительность)
Пользовательские Python-функции lambdaИспользуйте pandas или выполните преобразование на позднем этапе
Очень небольшой объём данных (<1 000 строк)Любой вариант (незначительная разница)
Совет

Для автоматического оптимального выбора движка используйте config.set_execution_engine('auto') (по умолчанию). Для максимальной пропускной способности при нагрузках с агрегацией используйте config.use_performance_mode(). Подробности см. в разделах Execution Engine и Performance Mode.