use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion};
use elif_orm::{
loading::{
batch_loader::{BatchConfig, BatchLoader},
eager_loader::OptimizedEagerLoader,
},
relationships::{
cache::RelationshipCache, eager_loader::EagerLoader, lazy_loading::LazyRelationshipLoader,
RelationshipMetadata, RelationshipType,
},
};
use serde_json::Value;
use std::collections::HashMap;
use tokio::runtime::Runtime;
#[derive(Clone, Debug)]
struct MockModel {
id: i32,
name: String,
parent_id: Option<i32>,
}
impl MockModel {
fn new(id: i32, name: &str, parent_id: Option<i32>) -> Self {
Self {
id,
name: name.to_string(),
parent_id,
}
}
}
fn generate_mock_data(count: usize) -> Vec<MockModel> {
(0..count)
.map(|i| MockModel::new(i as i32, &format!("Model_{}", i), Some((i / 2) as i32)))
.collect()
}
fn generate_relationship_metadata(relationship_type: RelationshipType) -> RelationshipMetadata {
RelationshipMetadata::builder()
.name("test_relationship")
.relationship_type(relationship_type)
.local_key("id")
.foreign_key("parent_id")
.related_table("related_models")
.build()
}
fn bench_eager_loader_creation(c: &mut Criterion) {
let mut group = c.benchmark_group("eager_loader_creation");
group.bench_function("basic_eager_loader", |b| {
b.iter(|| {
let loader = EagerLoader::new(
black_box("users".to_string()),
black_box(vec!["id".to_string(), "name".to_string()]),
);
black_box(loader)
})
});
group.bench_function("optimized_eager_loader", |b| {
b.iter(|| {
let config = elif_orm::loading::EagerLoadConfig::builder()
.batch_size(black_box(100))
.parallel_batches(black_box(true))
.cache_results(black_box(true))
.build_with_defaults();
let loader = OptimizedEagerLoader::new(config);
black_box(loader)
})
});
group.finish();
}
fn bench_batch_loader_operations(c: &mut Criterion) {
let mut group = c.benchmark_group("batch_loader_operations");
for &batch_size in &[10, 50, 100, 200] {
group.bench_with_input(
BenchmarkId::new("batch_configuration", batch_size),
&batch_size,
|b, &batch_size| {
b.iter(|| {
let config = BatchConfig::builder()
.batch_size(black_box(batch_size))
.max_concurrent_batches(black_box(4))
.enable_query_deduplication(black_box(true))
.build_with_defaults();
let loader = BatchLoader::new(config);
black_box(loader)
})
},
);
}
for &data_size in &[100, 500, 1000, 2000] {
group.bench_with_input(
BenchmarkId::new("simulate_batch_loading", data_size),
&data_size,
|b, &data_size| {
let models = generate_mock_data(data_size);
b.iter(|| {
let batch_size = 100;
let mut batches = Vec::new();
for chunk in models.chunks(batch_size) {
let ids: Vec<i32> = chunk.iter().map(|m| m.id).collect();
batches.push(ids);
}
for batch in &batches {
let _query = format!(
"SELECT * FROM related WHERE parent_id IN ({})",
batch
.iter()
.map(|id| id.to_string())
.collect::<Vec<_>>()
.join(",")
);
}
black_box(batches)
})
},
);
}
group.finish();
}
fn bench_relationship_cache_operations(c: &mut Criterion) {
let mut group = c.benchmark_group("relationship_cache_operations");
group.bench_function("cache_creation", |b| {
b.iter(|| {
let cache = RelationshipCache::new(black_box(1000));
black_box(cache)
})
});
for &cache_size in &[100, 500, 1000, 2000] {
group.bench_with_input(
BenchmarkId::new("cache_operations", cache_size),
&cache_size,
|b, &cache_size| {
let cache = RelationshipCache::new(cache_size);
b.iter(|| {
for i in 0..cache_size / 2 {
let key = format!("user:{}", i);
let value = vec![
serde_json::json!({"id": i, "name": format!("User {}", i)}),
serde_json::json!({"id": i + 1000, "name": format!("User {}", i + 1000)}),
];
cache.put(key, value);
}
let mut hits = 0;
for i in 0..cache_size {
let key = format!("user:{}", i % (cache_size / 2));
if cache.get(&key).is_some() {
hits += 1;
}
}
black_box(hits)
})
},
);
}
group.finish();
}
fn bench_relationship_hydration(c: &mut Criterion) {
let rt = Runtime::new().unwrap();
let mut group = c.benchmark_group("relationship_hydration");
let relationship_types = vec![
("has_one", RelationshipType::HasOne),
("has_many", RelationshipType::HasMany),
("belongs_to", RelationshipType::BelongsTo),
];
for (name, relationship_type) in relationship_types {
group.bench_function(&format!("hydrate_{}", name), |b| {
let metadata = generate_relationship_metadata(relationship_type);
let models = generate_mock_data(100);
b.iter(|| {
rt.block_on(async {
let mut hydrated_models = Vec::new();
for model in &models {
let mut model_data = HashMap::new();
model_data.insert("id".to_string(), Value::Number(model.id.into()));
model_data.insert("name".to_string(), Value::String(model.name.clone()));
let related_data = match relationship_type {
RelationshipType::HasMany => {
vec![
serde_json::json!({"id": model.id * 10, "parent_id": model.id}),
serde_json::json!({"id": model.id * 10 + 1, "parent_id": model.id}),
]
},
RelationshipType::HasOne => {
vec![serde_json::json!({"id": model.id + 1000, "parent_id": model.id})]
},
RelationshipType::BelongsTo => {
if let Some(parent_id) = model.parent_id {
vec![serde_json::json!({"id": parent_id, "name": format!("Parent {}", parent_id)})]
} else {
vec![]
}
},
_ => vec![],
};
model_data.insert(metadata.name.clone(), Value::Array(related_data));
hydrated_models.push(model_data);
}
black_box(hydrated_models)
})
})
});
}
group.finish();
}
fn bench_n_plus_one_prevention(c: &mut Criterion) {
let mut group = c.benchmark_group("n_plus_one_prevention");
for &model_count in &[10, 50, 100, 200] {
group.bench_with_input(
BenchmarkId::new("simulated_n_plus_one", model_count),
&model_count,
|b, &model_count| {
let models = generate_mock_data(model_count);
b.iter(|| {
let mut results = Vec::new();
for model in &models {
let query = format!("SELECT * FROM related WHERE parent_id = {}", model.id);
let _simulated_result = vec![format!("result_for_{}", model.id)];
results.push(query);
}
black_box(results)
})
},
);
group.bench_with_input(
BenchmarkId::new("simulated_batched_loading", model_count),
&model_count,
|b, &model_count| {
let models = generate_mock_data(model_count);
b.iter(|| {
let batch_size = 50;
let mut batch_queries = Vec::new();
for chunk in models.chunks(batch_size) {
let ids: Vec<String> = chunk.iter().map(|m| m.id.to_string()).collect();
let query = format!(
"SELECT * FROM related WHERE parent_id IN ({})",
ids.join(",")
);
batch_queries.push(query);
}
black_box(batch_queries)
})
},
);
}
group.finish();
}
fn bench_lazy_loading_patterns(c: &mut Criterion) {
let rt = Runtime::new().unwrap();
let mut group = c.benchmark_group("lazy_loading_patterns");
group.bench_function("lazy_loader_creation", |b| {
b.iter(|| {
let loader = LazyRelationshipLoader::new(
black_box("users".to_string()),
black_box("profiles".to_string()),
black_box("user_id".to_string()),
);
black_box(loader)
})
});
for &access_pattern in &[10, 25, 50, 100] {
group.bench_with_input(
BenchmarkId::new("lazy_access_simulation", access_pattern),
&access_pattern,
|b, &access_pattern| {
let models = generate_mock_data(access_pattern);
let cache = RelationshipCache::new(1000);
b.iter(|| {
rt.block_on(async {
let mut accessed_relationships = 0;
for model in &models {
let cache_key = format!("lazy:{}:{}", "profiles", model.id);
if let Some(_cached) = cache.get(&cache_key) {
accessed_relationships += 1;
} else {
let _query = format!("SELECT * FROM profiles WHERE user_id = {}", model.id);
let simulated_result = vec![
serde_json::json!({"id": model.id + 2000, "user_id": model.id, "bio": format!("Bio for user {}", model.id)})
];
cache.put(cache_key, simulated_result);
accessed_relationships += 1;
}
if model.id % 3 != 0 {
continue;
}
}
black_box(accessed_relationships)
})
})
},
);
}
group.finish();
}
criterion_group!(
benches,
bench_eager_loader_creation,
bench_batch_loader_operations,
bench_relationship_cache_operations,
bench_relationship_hydration,
bench_n_plus_one_prevention,
bench_lazy_loading_patterns
);
criterion_main!(benches);