DataStore ofrece un rendimiento significativamente superior al de pandas en muchas operaciones. Esta guía explica por qué y cómo optimizar sus cargas de trabajo.
Por qué DataStore es más rápido
1. SQL Pushdown
Se hace pushdown de las operaciones en la fuente de datos:
# 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 data2. Poda de columnas
Solo se leen las columnas necesarias:
# 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 selects3. Evaluación diferida
Varias operaciones se compilan en una sola consulta:
# 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 10Benchmark: DataStore frente a pandas
Entorno de prueba
- Datos: 10 millones de filas
- Hardware: portátil estándar
- Formato de archivo: CSV
Resultados
| Operación | pandas (ms) | DataStore (ms) | Mejor rendimiento |
|---|---|---|---|
| conteo con GroupBy | 347 | 17 | DataStore (19.93x) |
| Operaciones combinadas | 1,535 | 234 | DataStore (6.56x) |
| pipeline complejo | 2,047 | 380 | DataStore (5.39x) |
| MultiFilter+Sort+Head | 1,963 | 366 | DataStore (5.36x) |
| Filter+Sort+Head | 1,537 | 350 | DataStore (4.40x) |
| Head/Limit | 166 | 45 | DataStore (3.69x) |
| Ultracomplejo (10+ ops) | 1,070 | 338 | DataStore (3.17x) |
| agregación con GroupBy | 406 | 141 | DataStore (2.88x) |
| Select+Filter+Sort | 1,217 | 443 | DataStore (2.75x) |
| Filter+GroupBy+Sort | 466 | 184 | DataStore (2.53x) |
| Filter+Select+Sort | 1,285 | 533 | DataStore (2.41x) |
| Sort (simple) | 1,742 | 1,197 | DataStore (1.45x) |
| Filter (simple) | 276 | 526 | Comparable |
| Sort (múltiple) | 947 | 1,477 | Comparable |
Puntos clave
- Operaciones de GroupBy: DataStore hasta 19.93 veces más rápido
- Canalizaciones complejas: DataStore 5-6 veces más rápido (beneficio de SQL Pushdown)
- Operaciones de slicing simples: Rendimiento comparable; la diferencia es insignificante
- Mejor caso de uso: Operaciones de varios pasos con GroupBy/agregación
- Zero-copy:
to_df()no tiene sobrecarga por conversión de datos
Cuándo destaca DataStore
Agregaciones intensivas
# DataStore excels: 19.93x faster
result = ds.groupby('category')['amount'].sum()Canalizaciones complejas
# 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)
)Procesamiento de archivos grandes
# DataStore: Only loads what you need
ds = pd.read_parquet("huge_file.parquet")
result = ds.filter(ds['id'] == 12345).to_df() # Fast!Operaciones con varias columnas
# DataStore: Combines into single SQL
ds['total'] = ds['price'] * ds['quantity']
ds['is_large'] = ds['total'] > 1000
ds = ds.filter(ds['is_large'])Cuándo pandas ofrece un rendimiento comparable
En la mayoría de los casos, DataStore iguala o supera el rendimiento de pandas. Sin embargo, pandas puede ser ligeramente más rápido en estos casos concretos:
Conjuntos de datos pequeños (<1.000 filas)
# For very small datasets, overhead is minimal for both
# Performance difference is negligible
small_df = pd.DataFrame({'x': range(100)})Operaciones simples de slicing
# Single slice operations without aggregation
df = df[df['x'] > 10] # pandas slightly faster
ds = ds[ds['x'] > 10] # DataStore comparableFunciones lambda personalizadas en Python
# pandas required for custom Python code
def complex_function(row):
return custom_logic(row)
df['result'] = df.apply(complex_function, axis=1)Integración de DataFrames con zero-copy
DataStore utiliza zero-copy para leer y escribir DataFrames de pandas. Esto significa:
# 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 copyImplicaciones clave:
to_df()es prácticamente gratuito: no requiere serialización ni copia de memoria- Crear un DataStore a partir de un pandas DataFrame es instantáneo
- La memoria se comparte entre el DataStore y las vistas de pandas
Consejos de optimización
1. Habilite el modo de rendimiento para cargas de trabajo exigentes
Para cargas de trabajo con mucha agregación en las que no necesita el formato de salida exacto de pandas (orden de las filas, columnas MultiIndex, correcciones de dtype), habilite el modo de rendimiento para obtener el máximo rendimiento:
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']})
)Mejora esperada: Entre 2 y 8 veces más rápido para cargas de trabajo con filtros y GroupBy, con menor uso de memoria para archivos Parquet de gran tamaño.
Consulte modo de rendimiento para ver todos los detalles.
2. Utiliza Parquet en lugar de 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")Mejora esperada: lecturas entre 3 y 10 veces más rápidas
3. Filtre lo antes posible
# 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. Selecciona solo las columnas necesarias
# 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 columns5. Aproveche las agregaciones en SQL
# GroupBy is where DataStore shines
# Up to 20x speedup!
result = ds.groupby('category').agg({
'amount': ['sum', 'mean', 'count', 'max'],
'quantity': 'sum'
})6. Usa head() en lugar de consultas completas
# 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 everything7. Operaciones por lotes
# 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 again8. Usa explain() para optimizar
# 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()Perfilado de la carga de trabajo
Activar el perfilado
from chdb.datastore.config import config, get_profiler
config.enable_profiling()
# Run your workload
result = your_pipeline()
# View report
profiler = get_profiler()
profiler.report()Identificar cuellos de botella
Performance Report
==================
Step Duration % Total
---- -------- -------
SQL execution 2.5s 62.5% <- Bottleneck!
read_csv 1.2s 30.0%
Other 0.3s 7.5%Comparación de enfoques
# 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")Resumen de buenas prácticas
| Práctica | Impacto |
|---|---|
| Activar el modo de rendimiento | Entre 2 y 8 veces más rápido para workloads de agregación |
| Usar archivos Parquet | Lecturas entre 3 y 10 veces más rápidas |
| Filtrar cuanto antes | Reduce el procesamiento de datos |
| Seleccionar las columnas necesarias | Reduce la E/S y el uso de memoria |
| Usar GroupBy/agrupaciones | Hasta 20 veces más rápido |
| Operaciones por lotes | Evita ejecuciones repetidas |
| Perfilar antes de optimizar | Permite encontrar los verdaderos cuellos de botella |
| Usar explain() | Verifica la optimización de la consulta |
| Usar head() para muestras | Evita escaneos completos de tablas |
Guía rápida para decidir
| Tu carga de trabajo | Recomendación |
|---|---|
| GroupBy/agregación | Usa DataStore |
| Pipeline complejo de varios pasos | Usa DataStore |
| Archivos grandes con filtros | Usa DataStore |
| Operaciones simples de slicing | Cualquiera de los dos (rendimiento comparable) |
| Funciones lambda personalizadas de Python | Usa pandas o convierte más adelante |
| Datos muy pequeños (<1,000 filas) | Cualquiera de los dos (diferencia insignificante) |