DataStore offre des gains de performance significatifs par rapport à pandas pour de nombreuses opérations. Ce guide explique pourquoi et comment optimiser vos charges de travail.
Pourquoi DataStore est plus rapide
1. SQL Pushdown
Les opérations sont exécutées directement sur la source de données :
# 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. Sélection des colonnes nécessaires
Seules les colonnes nécessaires sont lues :
# 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. Évaluation différée
Plusieurs opérations se compilent en une seule requête :
# 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 vs pandas
Environnement de test
- Données : 10 millions de lignes
- Matériel : ordinateur portable standard
- Format de fichier : CSV
Résultats
| Opération | pandas (ms) | DataStore (ms) | Plus rapide |
|---|---|---|---|
| Count GroupBy | 347 | 17 | DataStore (19.93x) |
| Opérations combinées | 1,535 | 234 | DataStore (6.56x) |
| Pipeline complexe | 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) |
| Ultra-complexe (10+ ops) | 1,070 | 338 | DataStore (3.17x) |
| Agrégation 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 (multiple) | 947 | 1,477 | Comparable |
Points clés
- Opérations GroupBy : DataStore jusqu'à 19,93x plus rapide
- Pipelines complexes : DataStore 5 à 6x plus rapide (avantage du SQL pushdown)
- Opérations de découpage simples : performances comparables - différence négligeable
- Meilleur cas d'utilisation : opérations en plusieurs étapes avec groupby/agrégation
- Zero-copy :
to_df()n'entraîne aucun surcoût de conversion des données
Quand DataStore est préférable
Agrégations intensives
# DataStore excels: 19.93x faster
result = ds.groupby('category')['amount'].sum()Pipelines complexes
# 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)
)Traitement des fichiers volumineux
# DataStore: Only loads what you need
ds = pd.read_parquet("huge_file.parquet")
result = ds.filter(ds['id'] == 12345).to_df() # Fast!Opérations sur plusieurs colonnes
# DataStore: Combines into single SQL
ds['total'] = ds['price'] * ds['quantity']
ds['is_large'] = ds['total'] > 1000
ds = ds.filter(ds['is_large'])Quand pandas tient la comparaison
Dans la plupart des cas, DataStore égale ou dépasse les performances de pandas. Toutefois, pandas peut être légèrement plus rapide dans les cas suivants :
Petits jeux de données (<1 000 lignes)
# For very small datasets, overhead is minimal for both
# Performance difference is negligible
small_df = pd.DataFrame({'x': range(100)})Opérations de découpage simples
# Single slice operations without aggregation
df = df[df['x'] > 10] # pandas slightly faster
ds = ds[ds['x'] > 10] # DataStore comparableFonctions lambda Python personnalisées
# pandas required for custom Python code
def complex_function(row):
return custom_logic(row)
df['result'] = df.apply(complex_function, axis=1)Intégration zero-copy des DataFrames
DataStore utilise le zero-copy pour lire et écrire des DataFrames pandas. Cela signifie :
# 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 copyPrincipales implications :
to_df()est pratiquement gratuit - sans sérialisation ni copie en mémoire- La création d’un DataStore à partir d’un pandas DataFrame est instantanée
- La mémoire est partagée entre DataStore et les vues pandas
Conseils d’optimisation
1. Activer le mode Performance pour les charges de travail intensives
Pour les charges de travail fortement axées sur l’agrégation, lorsque vous n’avez pas besoin du format de sortie exact de pandas (ordre des lignes, colonnes MultiIndex, corrections de dtype), activez le mode Performance pour un débit maximal :
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']})
)Amélioration attendue : jusqu’à 2 à 8 fois plus rapide pour les charges de travail filter+groupby, avec une consommation mémoire réduite pour les fichiers Parquet volumineux.
Voir Mode Performance pour plus de détails.
2. Utilisez Parquet plutôt que 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")Amélioration attendue : lectures 3 à 10 fois plus rapides
3. Filtrez dès le départ
# 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. Sélectionnez uniquement les colonnes nécessaires
# 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. Exploitez les agrégations SQL
# GroupBy is where DataStore shines
# Up to 20x speedup!
result = ds.groupby('category').agg({
'amount': ['sum', 'mean', 'count', 'max'],
'quantity': 'sum'
})6. Utilisez head() au lieu de requêtes complètes
# 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. Opérations par lots
# 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. Utilisez explain() pour optimiser
# 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()Profilage de votre charge de travail
Activer le profilage
from chdb.datastore.config import config, get_profiler
config.enable_profiling()
# Run your workload
result = your_pipeline()
# View report
profiler = get_profiler()
profiler.report()Identifier les goulots d’étranglement
Performance Report
==================
Step Duration % Total
---- -------- -------
SQL execution 2.5s 62.5% <- Bottleneck!
read_csv 1.2s 30.0%
Other 0.3s 7.5%Comparaison des approches
# 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")Résumé des bonnes pratiques
| Pratique | Impact |
|---|---|
| Activer le mode performance | 2 à 8x plus rapide pour les charges de travail d’agrégation |
| Utiliser des fichiers Parquet | Lectures 3 à 10x plus rapides |
| Filtrer dès le début | Réduit le traitement des données |
| Sélectionner les colonnes nécessaires | Réduit les E/S et l’utilisation de la mémoire |
| Utiliser GroupBy/agrégations | Jusqu’à 20x plus rapide |
| Regrouper les opérations par lots | Évite les exécutions répétées |
| Profiler avant d’optimiser | Identifier les véritables goulots d’étranglement |
| Utiliser explain() | Vérifier l’optimisation des requêtes |
| Utiliser head() pour les échantillons | Évite les parcours complets de la table |
Guide de décision rapide
| Votre charge de travail | Recommandation |
|---|---|
| GroupBy/agrégation | Utilisez DataStore |
| Pipeline complexe en plusieurs étapes | Utilisez DataStore |
| Fichiers volumineux avec filtres | Utilisez DataStore |
| Opérations simples de découpage | Les deux (performances comparables) |
| Fonctions lambda Python personnalisées | Utilisez pandas ou convertissez ultérieurement |
| Très petits jeux de données (<1 000 lignes) | Les deux (différence négligeable) |