#![cfg(feature = "memcached")]
use cache_kit::backend::{CacheBackend, MemcachedBackend};
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
use std::hint::black_box;
async fn setup_memcached() -> MemcachedBackend {
MemcachedBackend::from_server("localhost:11211".to_string())
.await
.expect(
"Failed to connect to Memcached at localhost:11211. Make sure Memcached is running.",
)
}
fn memcached_basic_benchmarks(c: &mut Criterion) {
let mut group = c.benchmark_group("memcached_backend");
group.sample_size(50);
let rt = tokio::runtime::Runtime::new().expect("Failed to create Tokio runtime");
let backend = rt.block_on(async { setup_memcached().await });
rt.block_on(async { backend.clear_all().await })
.expect("Failed to clear Memcached");
for size in [100, 1_000, 10_000, 100_000].iter() {
group
.throughput(Throughput::Bytes(*size as u64))
.bench_with_input(BenchmarkId::new("set", size), size, |b, &size| {
let value = vec![1u8; size];
b.to_async(&rt).iter(|| async {
backend
.set(
black_box("memcached_bench_key"),
black_box(value.clone()),
None,
)
.await
.expect("Failed to set")
});
});
group
.throughput(Throughput::Bytes(*size as u64))
.bench_with_input(BenchmarkId::new("get_hit", size), size, |b, &size| {
let value = vec![1u8; size];
rt.block_on(async {
backend
.set("memcached_bench_key", value, None)
.await
.expect("Failed to set");
});
b.to_async(&rt).iter(|| async {
backend
.get(black_box("memcached_bench_key"))
.await
.expect("Failed to get")
});
});
}
group.bench_function("get_miss", |b| {
b.to_async(&rt).iter(|| async {
backend
.get(black_box("nonexistent_key"))
.await
.expect("Failed to get")
});
});
group.bench_function("delete", |b| {
let value = vec![1u8; 1000];
b.to_async(&rt).iter(|| async {
backend
.set("memcached_bench_delete", value.clone(), None)
.await
.expect("Failed to set");
backend
.delete(black_box("memcached_bench_delete"))
.await
.expect("Failed to delete")
});
});
group.bench_function("exists", |b| {
rt.block_on(async {
backend
.set("memcached_bench_exists", vec![1u8; 1000], None)
.await
.expect("Failed to set");
});
b.to_async(&rt).iter(|| async {
backend
.exists(black_box("memcached_bench_exists"))
.await
.expect("Failed to check exists")
});
});
rt.block_on(async { backend.clear_all().await })
.expect("Failed to clear Memcached");
group.finish();
}
fn memcached_batch_benchmarks(c: &mut Criterion) {
let mut group = c.benchmark_group("memcached_batch_ops");
group.sample_size(50);
let rt = tokio::runtime::Runtime::new().expect("Failed to create Tokio runtime");
let backend = rt.block_on(async { setup_memcached().await });
rt.block_on(async { backend.clear_all().await })
.expect("Failed to clear Memcached");
for batch_size in [10, 50, 100].iter() {
for payload_size in [100, 1_000, 10_000].iter() {
let keys: Vec<String> = (0..*batch_size)
.map(|i| format!("memcached_mget_key_{}", i))
.collect();
let value = vec![1u8; *payload_size];
rt.block_on(async {
for key in &keys {
backend
.set(key, value.clone(), None)
.await
.expect("Failed to set");
}
});
let key_refs: Vec<&str> = keys.iter().map(|s| s.as_str()).collect();
group
.throughput(Throughput::Bytes((*batch_size * *payload_size) as u64))
.bench_with_input(
BenchmarkId::new(
"mget",
format!("batch_{}_size_{}", batch_size, payload_size),
),
&key_refs,
|b, keys| {
b.to_async(&rt).iter(|| async {
backend.mget(black_box(keys)).await.expect("Failed to mget")
});
},
);
}
}
for batch_size in [10, 50, 100].iter() {
group.bench_with_input(
BenchmarkId::new("mdelete", batch_size),
batch_size,
|b, &batch_size| {
b.to_async(&rt).iter(|| async {
let keys: Vec<String> = (0..batch_size)
.map(|i| format!("memcached_mdelete_key_{}", i))
.collect();
for key in &keys {
backend
.set(key, vec![1u8; 100], None)
.await
.expect("Failed to set");
}
let key_refs: Vec<&str> = keys.iter().map(|s| s.as_str()).collect();
backend
.mdelete(black_box(&key_refs))
.await
.expect("Failed to mdelete")
});
},
);
}
rt.block_on(async { backend.clear_all().await })
.expect("Failed to clear Memcached");
group.finish();
}
fn memcached_protocol_benchmarks(c: &mut Criterion) {
let mut group = c.benchmark_group("memcached_protocol");
group.sample_size(50);
let rt = tokio::runtime::Runtime::new().expect("Failed to create Tokio runtime");
let backend = rt.block_on(async { setup_memcached().await });
rt.block_on(async { backend.clear_all().await })
.expect("Failed to clear Memcached");
group.bench_function("rapid_set_get_delete", |b| {
let value = vec![1u8; 1000];
b.to_async(&rt).iter(|| async {
let key = "memcached_protocol_test";
backend
.set(black_box(key), black_box(value.clone()), None)
.await
.expect("Failed to set");
let _ = backend.get(black_box(key)).await.expect("Failed to get");
backend
.delete(black_box(key))
.await
.expect("Failed to delete");
});
});
group.bench_function("health_check", |b| {
b.to_async(&rt)
.iter(|| async { backend.health_check().await.expect("Failed health check") });
});
rt.block_on(async { backend.clear_all().await })
.expect("Failed to clear Memcached");
group.finish();
}
fn memcached_ttl_benchmarks(c: &mut Criterion) {
let mut group = c.benchmark_group("memcached_ttl");
group.sample_size(50);
let rt = tokio::runtime::Runtime::new().expect("Failed to create Tokio runtime");
let backend = rt.block_on(async { setup_memcached().await });
rt.block_on(async { backend.clear_all().await })
.expect("Failed to clear Memcached");
for size in [100, 1_000, 10_000].iter() {
group
.throughput(Throughput::Bytes(*size as u64))
.bench_with_input(BenchmarkId::new("set_with_ttl", size), size, |b, &size| {
let value = vec![1u8; size];
let ttl = Some(std::time::Duration::from_secs(60));
b.to_async(&rt).iter(|| async {
backend
.set(
black_box("memcached_ttl_key"),
black_box(value.clone()),
black_box(ttl),
)
.await
.expect("Failed to set with TTL")
});
});
}
rt.block_on(async { backend.clear_all().await })
.expect("Failed to clear Memcached");
group.finish();
}
criterion_group!(
benches,
memcached_basic_benchmarks,
memcached_batch_benchmarks,
memcached_protocol_benchmarks,
memcached_ttl_benchmarks
);
criterion_main!(benches);