reasonkit-core 0.1.8

The Reasoning Engine — Auditable Reasoning for Production AI | Rust-Native | Turn Prompts into Protocols
//! Performance benchmarks for optimized Qdrant operations
//!
//! Validates the performance improvements from:
//! - Batch upsert operations
//! - Query result caching
//! - Filter optimization
//! - Connection pooling

use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
// Note: The optimized storage module is temporarily disabled.
// This benchmark uses simulated operations to validate benchmark infrastructure.
use std::time::Duration;
use tokio::runtime::Runtime;
use uuid::Uuid;

/// Helper to create a test runtime
fn create_runtime() -> Runtime {
    Runtime::new().unwrap()
}

/// Generate test embeddings
fn generate_embeddings(count: usize, vector_size: usize) -> Vec<(Uuid, Vec<f32>)> {
    (0..count)
        .map(|i| {
            let id = Uuid::new_v4();
            let embedding = (0..vector_size)
                .map(|j| ((i + j) as f32 * 0.1).sin())
                .collect();
            (id, embedding)
        })
        .collect()
}

/// Generate test query vectors
fn generate_query_vectors(count: usize, vector_size: usize) -> Vec<Vec<f32>> {
    (0..count)
        .map(|i| {
            (0..vector_size)
                .map(|j| ((i + j) as f32 * 0.1).cos())
                .collect()
        })
        .collect()
}

/// Benchmark batch upsert operations
fn bench_batch_upsert(c: &mut Criterion) {
    let rt = create_runtime();

    // Note: This benchmark requires a running Qdrant instance
    // For CI/CD, this should be skipped or use a mock
    let mut group = c.benchmark_group("batch_upsert");
    group.measurement_time(Duration::from_secs(10));

    for batch_size in [10, 50, 100, 500, 1000].iter() {
        group.throughput(Throughput::Elements(*batch_size as u64));

        group.bench_with_input(
            BenchmarkId::from_parameter(batch_size),
            batch_size,
            |b, &size| {
                b.to_async(&rt).iter(|| async move {
                    // In a real scenario, this would connect to Qdrant
                    // For benchmarking, we simulate the operation
                    let embeddings = generate_embeddings(size, 768);

                    // Simulate batch processing time
                    // In production, this would be actual Qdrant calls
                    tokio::time::sleep(Duration::from_micros(100 * size as u64)).await;

                    black_box(embeddings)
                });
            },
        );
    }

    group.finish();
}

/// Benchmark query caching performance
fn bench_query_cache(c: &mut Criterion) {
    let rt = create_runtime();

    let mut group = c.benchmark_group("query_cache");
    group.measurement_time(Duration::from_secs(10));

    // Benchmark cache hit performance
    group.bench_function("cache_hit", |b| {
        b.to_async(&rt).iter(|| async move {
            let query_vector = generate_query_vectors(1, 768).pop().unwrap();

            // Simulate cache lookup (< 1ms expected)
            tokio::time::sleep(Duration::from_micros(10)).await;

            black_box(query_vector)
        });
    });

    // Benchmark cache miss performance
    group.bench_function("cache_miss", |b| {
        b.to_async(&rt).iter(|| async move {
            let query_vector = generate_query_vectors(1, 768).pop().unwrap();

            // Simulate Qdrant query (50-100ms expected)
            tokio::time::sleep(Duration::from_millis(50)).await;

            black_box(query_vector)
        });
    });

    group.finish();
}

/// Benchmark parallel vs sequential batch processing
fn bench_parallel_batching(c: &mut Criterion) {
    let rt = create_runtime();

    let mut group = c.benchmark_group("parallel_batching");
    group.measurement_time(Duration::from_secs(15));

    let total_embeddings = 1000;
    let batch_size = 100;

    // Sequential processing
    group.bench_function("sequential", |b| {
        b.to_async(&rt).iter(|| async move {
            let embeddings = generate_embeddings(total_embeddings, 768);
            let batches: Vec<_> = embeddings.chunks(batch_size).collect();

            for batch in batches {
                // Simulate batch upsert
                tokio::time::sleep(Duration::from_millis(10)).await;
                black_box(batch);
            }
        });
    });

    // Parallel processing
    group.bench_function("parallel", |b| {
        b.to_async(&rt).iter(|| async move {
            let embeddings = generate_embeddings(total_embeddings, 768);
            let batches: Vec<_> = embeddings.chunks(batch_size).collect();

            let futures: Vec<_> = batches
                .into_iter()
                .map(|batch| async move {
                    // Simulate batch upsert
                    tokio::time::sleep(Duration::from_millis(10)).await;
                    black_box(batch);
                })
                .collect();

            futures::future::join_all(futures).await;
        });
    });

    group.finish();
}

/// Benchmark cache warming performance
fn bench_cache_warming(c: &mut Criterion) {
    let rt = create_runtime();

    let mut group = c.benchmark_group("cache_warming");
    group.measurement_time(Duration::from_secs(10));

    for query_count in [10, 50, 100].iter() {
        group.bench_with_input(
            BenchmarkId::from_parameter(query_count),
            query_count,
            |b, &count| {
                b.to_async(&rt).iter(|| async move {
                    let queries = generate_query_vectors(count, 768);

                    // Simulate warming cache for hot queries
                    for query in queries {
                        tokio::time::sleep(Duration::from_micros(50)).await;
                        black_box(query);
                    }
                });
            },
        );
    }

    group.finish();
}

/// Benchmark vector similarity computation
fn bench_vector_similarity(c: &mut Criterion) {
    let mut group = c.benchmark_group("vector_similarity");

    for vector_size in [384, 768, 1536].iter() {
        group.bench_with_input(
            BenchmarkId::from_parameter(vector_size),
            vector_size,
            |b, &size| {
                let v1: Vec<f32> = (0..size).map(|i| (i as f32 * 0.1).sin()).collect();
                let v2: Vec<f32> = (0..size).map(|i| (i as f32 * 0.1).cos()).collect();

                b.iter(|| {
                    // Cosine similarity
                    let dot_product: f32 = v1.iter().zip(v2.iter()).map(|(a, b)| a * b).sum();
                    let magnitude1: f32 = v1.iter().map(|x| x * x).sum::<f32>().sqrt();
                    let magnitude2: f32 = v2.iter().map(|x| x * x).sum::<f32>().sqrt();
                    black_box(dot_product / (magnitude1 * magnitude2))
                });
            },
        );
    }

    group.finish();
}

/// Benchmark cache key generation
fn bench_cache_key_generation(c: &mut Criterion) {
    let mut group = c.benchmark_group("cache_key_generation");

    for vector_size in [384, 768, 1536].iter() {
        group.bench_with_input(
            BenchmarkId::from_parameter(vector_size),
            vector_size,
            |b, &size| {
                let vector: Vec<f32> = (0..size).map(|i| (i as f32 * 0.1).sin()).collect();

                b.iter(|| {
                    use std::collections::hash_map::DefaultHasher;
                    use std::hash::{Hash, Hasher};

                    let mut hasher = DefaultHasher::new();
                    // Hash first 8 elements for cache key
                    vector.iter().take(8).for_each(|&f| {
                        f.to_bits().hash(&mut hasher);
                    });
                    black_box(hasher.finish())
                });
            },
        );
    }

    group.finish();
}

/// Benchmark LRU cache operations
fn bench_lru_operations(c: &mut Criterion) {
    use std::collections::HashMap;

    let mut group = c.benchmark_group("lru_operations");

    // Benchmark cache insertion
    group.bench_function("insert", |b| {
        b.iter(|| {
            let mut cache: HashMap<u64, Vec<f32>> = HashMap::new();
            for i in 0..1000 {
                let vector = vec![i as f32; 768];
                cache.insert(i, vector);
            }
            black_box(cache)
        });
    });

    // Benchmark cache lookup
    group.bench_function("lookup", |b| {
        let mut cache: HashMap<u64, Vec<f32>> = HashMap::new();
        for i in 0..1000 {
            cache.insert(i, vec![i as f32; 768]);
        }

        b.iter(|| {
            for i in 0..100 {
                black_box(cache.get(&i));
            }
        });
    });

    group.finish();
}

/// Benchmark filter construction
fn bench_filter_construction(c: &mut Criterion) {
    let mut group = c.benchmark_group("filter_construction");

    group.bench_function("simple_filter", |b| {
        b.iter(|| {
            // Simulate constructing a Qdrant filter
            let _filter = format!(
                r#"{{"must": [{{"key": "chunk_id", "match": {{"value": "{}"}}}}]}}"#,
                Uuid::new_v4()
            );
            black_box(_filter)
        });
    });

    group.bench_function("complex_filter", |b| {
        b.iter(|| {
            // Simulate constructing a complex Qdrant filter
            let _filter = format!(
                r#"{{"must": [{{"key": "chunk_id", "match": {{"value": "{}"}}}}, {{"key": "document_type", "match": {{"value": "paper"}}}}], "should": [{{"key": "score", "range": {{"gte": 0.8}}}}]}}"#,
                Uuid::new_v4()
            );
            black_box(_filter)
        });
    });

    group.finish();
}

criterion_group!(
    benches,
    bench_batch_upsert,
    bench_query_cache,
    bench_parallel_batching,
    bench_cache_warming,
    bench_vector_similarity,
    bench_cache_key_generation,
    bench_lru_operations,
    bench_filter_construction,
);

criterion_main!(benches);