use std::time::Instant;
use qail_qdrant::prelude::Distance;
use qail_qdrant::{QdrantDriver, Point};
use qdrant_client::Qdrant;
use qdrant_client::qdrant::SearchPointsBuilder;
const COLLECTION_NAME: &str = "benchmark_collection";
const VECTOR_DIM: usize = 1536; const NUM_POINTS: usize = 1000;
const NUM_SEARCHES: usize = 1000;
const BATCH_SIZE: usize = 50;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("╔══════════════════════════════════════════════════════════════╗");
println!("║ QAIL vs Official qdrant-client: Full Benchmark Suite ║");
println!("╚══════════════════════════════════════════════════════════════╝\n");
println!("📦 Setting up benchmark...");
let mut rest_driver = QdrantDriver::connect("localhost", 6333).await?;
let mut grpc_driver = QdrantDriver::connect("localhost", 6334).await?;
let official_client = Qdrant::from_url("http://localhost:6334").build()?;
let _ = rest_driver.delete_collection(COLLECTION_NAME).await;
rest_driver
.create_collection(COLLECTION_NAME, VECTOR_DIM as u64, Distance::Cosine, false)
.await?;
println!(" ✓ Collection '{}' created ({} dimensions)", COLLECTION_NAME, VECTOR_DIM);
println!(" ✓ Generating {} test points with {}D vectors...", NUM_POINTS, VECTOR_DIM);
let points: Vec<Point> = (0..NUM_POINTS)
.map(|i| {
let vector: Vec<f32> = (0..VECTOR_DIM)
.map(|j| ((i + j) as f32 / VECTOR_DIM as f32).sin())
.collect();
Point::new_num(i as u64, vector)
.with_payload("index", i as i64)
})
.collect();
rest_driver.upsert(COLLECTION_NAME, &points, true).await?;
println!(" ✓ Points inserted\n");
let query_vectors: Vec<Vec<f32>> = (0..NUM_SEARCHES)
.map(|i| {
(0..VECTOR_DIM)
.map(|j| ((i * 7 + j) as f32 / VECTOR_DIM as f32).cos())
.collect()
})
.collect();
println!("═══════════════════════════════════════════════════════════════");
println!("📊 Test 1: Single Query Latency ({} queries)", NUM_SEARCHES);
println!("───────────────────────────────────────────────────────────────");
let official_start = Instant::now();
for vector in &query_vectors {
let _ = official_client.search_points(
SearchPointsBuilder::new(COLLECTION_NAME, vector.clone(), 10)
).await?;
}
let official_duration = official_start.elapsed();
let official_per_op = official_duration / NUM_SEARCHES as u32;
let qail_start = Instant::now();
for vector in &query_vectors {
let _ = grpc_driver.search(COLLECTION_NAME, vector, 10, None).await?;
}
let qail_duration = qail_start.elapsed();
let qail_per_op = qail_duration / NUM_SEARCHES as u32;
println!(" Official: {:?}/query", official_per_op);
println!(" QAIL: {:?}/query", qail_per_op);
println!(" Result: QAIL is {:.2}x faster\n",
official_duration.as_secs_f64() / qail_duration.as_secs_f64());
println!("═══════════════════════════════════════════════════════════════");
println!("📊 Test 2: Pipeline ({} batches of {} queries)", NUM_SEARCHES / BATCH_SIZE, BATCH_SIZE);
println!("───────────────────────────────────────────────────────────────");
let official_batch_start = Instant::now();
for chunk in query_vectors.chunks(BATCH_SIZE) {
let mut tasks = Vec::new();
for vector in chunk {
let client = official_client.clone();
let vec = vector.clone();
tasks.push(tokio::spawn(async move {
client.search_points(
SearchPointsBuilder::new(COLLECTION_NAME, vec, 10)
).await
}));
}
for task in tasks {
let _ = task.await?;
}
}
let official_batch_duration = official_batch_start.elapsed();
let qail_batch_start = Instant::now();
for chunk in query_vectors.chunks(BATCH_SIZE) {
let mut tasks = Vec::new();
for vector in chunk {
let vec = vector.clone();
tasks.push(tokio::spawn(async move {
let mut driver = QdrantDriver::connect("localhost", 6334).await?;
driver.search(COLLECTION_NAME, &vec, 10, None).await
}));
}
for task in tasks {
let _ = task.await?;
}
}
let qail_batch_duration = qail_batch_start.elapsed();
println!(" Official: {:?} total ({:?}/batch)",
official_batch_duration,
official_batch_duration / (NUM_SEARCHES / BATCH_SIZE) as u32);
println!(" QAIL: {:?} total ({:?}/batch)",
qail_batch_duration,
qail_batch_duration / (NUM_SEARCHES / BATCH_SIZE) as u32);
println!(" Result: QAIL is {:.2}x faster\n",
official_batch_duration.as_secs_f64() / qail_batch_duration.as_secs_f64());
println!("═══════════════════════════════════════════════════════════════");
println!("📈 FINAL SUMMARY");
println!("───────────────────────────────────────────────────────────────");
println!(" Test 1 (Single): QAIL {:.2}x faster",
official_duration.as_secs_f64() / qail_duration.as_secs_f64());
println!(" Test 2 (Pipeline): QAIL {:.2}x faster",
official_batch_duration.as_secs_f64() / qail_batch_duration.as_secs_f64());
println!("\n🧹 Cleaning up...");
rest_driver.delete_collection(COLLECTION_NAME).await?;
println!(" ✓ Collection deleted\n");
println!("╔══════════════════════════════════════════════════════════════╗");
println!("║ Benchmark Complete! ║");
println!("╚══════════════════════════════════════════════════════════════╝");
Ok(())
}