use criterion::{Criterion, criterion_group, criterion_main};
use futures_util::future::join_all;
use rustis::client::Client;
use rustis::resp::{Command, FastPathCommandBuilder, cmd};
use std::hint::black_box;
const KEY: &str = "user:123456789:session";
fn slow_path_get(key: &str) -> Command {
cmd("GET").key(key).into()
}
fn fast_path_get(key: &str) -> Command {
FastPathCommandBuilder::get(key)
}
fn heavy_generic_hset(fields: &[(String, String)]) -> Command {
let mut builder = cmd("HSET").key(KEY);
for (f, v) in fields {
builder = builder.arg(f).arg(v);
}
builder.into()
}
fn bench_get_commands(c: &mut Criterion) {
let mut group = c.benchmark_group("Redis GET");
let key = KEY;
group.bench_function("Slow Path (Generic)", |b| {
b.iter(|| black_box(slow_path_get(black_box(key))));
});
group.bench_function("Fast Path (Static Header)", |b| {
b.iter(|| black_box(fast_path_get(black_box(key))));
});
let fields: Vec<(String, String)> = (0..20)
.map(|i| (format!("f{i}"), format!("v{i}")))
.collect();
group.bench_function("Heavy Generic (HSET 20 fields)", |b| {
b.iter(|| black_box(heavy_generic_hset(black_box(&fields))));
});
group.finish();
}
fn build_runtime() -> tokio::runtime::Runtime {
tokio::runtime::Builder::new_multi_thread()
.worker_threads(4)
.enable_all()
.build()
.unwrap()
}
async fn saturate(client: &Client, tasks: usize, reqs: usize, build: fn(&str) -> Command) {
let handles: Vec<_> = (0..tasks)
.map(|_| {
let client = client.clone();
tokio::spawn(async move {
for _ in 0..reqs {
let _: String = client.send(build(KEY), None).await.unwrap();
}
})
})
.collect();
join_all(handles).await;
}
fn bench_get_e2e(c: &mut Criterion) {
const TASKS: usize = 50;
const REQS: usize = 20;
let rt = build_runtime();
let host = std::env::var("REDIS_HOST").unwrap_or_else(|_| "127.0.0.1".to_string());
let client = rt.block_on(async {
let client = Client::connect(host.as_str()).await.unwrap();
let _: String = client
.send(cmd("SET").arg(KEY).arg("v"), None)
.await
.unwrap();
client
});
let mut group = c.benchmark_group("Redis GET E2E (saturated)");
group.bench_function("Generic path", |b| {
b.iter(|| rt.block_on(saturate(&client, TASKS, REQS, slow_path_get)));
});
group.bench_function("Fast path", |b| {
b.iter(|| rt.block_on(saturate(&client, TASKS, REQS, fast_path_get)));
});
group.finish();
}
async fn pipelined(client: &Client, count: usize, build: fn(&str) -> Command) {
let mut pipeline = client.create_pipeline();
for _ in 0..count {
pipeline.queue(build(KEY));
}
let _: Vec<String> = pipeline.execute().await.unwrap();
}
fn bench_get_pipeline(c: &mut Criterion) {
const COUNT: usize = 10_000;
let rt = build_runtime();
let host = std::env::var("REDIS_HOST").unwrap_or_else(|_| "127.0.0.1".to_string());
let client = rt.block_on(async {
let client = Client::connect(host.as_str()).await.unwrap();
let _: String = client
.send(cmd("SET").arg(KEY).arg("v"), None)
.await
.unwrap();
client
});
let mut group = c.benchmark_group("Redis GET pipeline (10k queued)");
group.bench_function("Generic path", |b| {
b.iter(|| rt.block_on(pipelined(&client, COUNT, slow_path_get)));
});
group.bench_function("Fast path", |b| {
b.iter(|| rt.block_on(pipelined(&client, COUNT, fast_path_get)));
});
group.finish();
}
criterion_group!(
benches,
bench_get_commands,
bench_get_e2e,
bench_get_pipeline
);
criterion_main!(benches);