use criterion::{criterion_group, criterion_main, BenchmarkGroup, BenchmarkId, Criterion};
use oxisql_core::{Connection, Value};
use oxisql_postgres::types::value_to_param;
use oxisql_postgres::{PgConnection, TlsMode};
fn bench_value_to_param_int(c: &mut Criterion) {
let val = Value::I64(42);
c.bench_function("value_to_param_i64", |b| {
b.iter(|| std::hint::black_box(value_to_param(&val)))
});
}
fn bench_value_to_param_text(c: &mut Criterion) {
let val = Value::Text("hello world".to_string());
c.bench_function("value_to_param_text", |b| {
b.iter(|| std::hint::black_box(value_to_param(&val)))
});
}
fn bench_value_to_param_float(c: &mut Criterion) {
let val = Value::F64(std::f64::consts::PI);
c.bench_function("value_to_param_f64", |b| {
b.iter(|| std::hint::black_box(value_to_param(&val)))
});
}
fn bench_value_to_param_null(c: &mut Criterion) {
let val = Value::Null;
c.bench_function("value_to_param_null", |b| {
b.iter(|| std::hint::black_box(value_to_param(&val)))
});
}
fn bench_value_to_param_bool(c: &mut Criterion) {
let val = Value::Bool(true);
c.bench_function("value_to_param_bool", |b| {
b.iter(|| std::hint::black_box(value_to_param(&val)))
});
}
fn bench_value_to_param_all_types(c: &mut Criterion) {
let mut group: BenchmarkGroup<_> = c.benchmark_group("value_to_param_all_types");
let val_null = Value::Null;
group.bench_function("null", |b| {
b.iter(|| std::hint::black_box(value_to_param(&val_null)))
});
let val_bool_true = Value::Bool(true);
group.bench_function("bool_true", |b| {
b.iter(|| std::hint::black_box(value_to_param(&val_bool_true)))
});
let val_bool_false = Value::Bool(false);
group.bench_function("bool_false", |b| {
b.iter(|| std::hint::black_box(value_to_param(&val_bool_false)))
});
let val_i64_max = Value::I64(i64::MAX);
group.bench_function("i64_max", |b| {
b.iter(|| std::hint::black_box(value_to_param(&val_i64_max)))
});
let val_i64_min = Value::I64(i64::MIN);
group.bench_function("i64_min", |b| {
b.iter(|| std::hint::black_box(value_to_param(&val_i64_min)))
});
let val_f64 = Value::F64(std::f64::consts::PI);
group.bench_function("f64_pi", |b| {
b.iter(|| std::hint::black_box(value_to_param(&val_f64)))
});
let val_text_short = Value::Text("hello world".to_string());
group.bench_function("text_short", |b| {
b.iter(|| std::hint::black_box(value_to_param(&val_text_short)))
});
let val_text_long = Value::Text("a".repeat(1024));
group.bench_function("text_long_1024", |b| {
b.iter(|| std::hint::black_box(value_to_param(&val_text_long)))
});
let val_blob_small = Value::Blob(vec![0xde, 0xad, 0xbe, 0xef]);
group.bench_function("blob_small_4b", |b| {
b.iter(|| std::hint::black_box(value_to_param(&val_blob_small)))
});
let val_blob_large = Value::Blob(vec![0u8; 1024]);
group.bench_function("blob_large_1024b", |b| {
b.iter(|| std::hint::black_box(value_to_param(&val_blob_large)))
});
let val_timestamp = Value::Timestamp(1_700_000_000_000_000);
group.bench_function("timestamp", |b| {
b.iter(|| std::hint::black_box(value_to_param(&val_timestamp)))
});
let val_date = Value::Date(19_000);
group.bench_function("date", |b| {
b.iter(|| std::hint::black_box(value_to_param(&val_date)))
});
let val_time = Value::Time(43_200_000_000); group.bench_function("time", |b| {
b.iter(|| std::hint::black_box(value_to_param(&val_time)))
});
let val_uuid = Value::Uuid(0x550e_8400_e29b_41d4_a716_4466_5544_0000_u128);
group.bench_function("uuid", |b| {
b.iter(|| std::hint::black_box(value_to_param(&val_uuid)))
});
let val_json = Value::Json(r#"{"key":"value","num":42}"#.to_string());
group.bench_function("json", |b| {
b.iter(|| std::hint::black_box(value_to_param(&val_json)))
});
let val_decimal = Value::Decimal("123456789.123456789".to_string());
group.bench_function("decimal", |b| {
b.iter(|| std::hint::black_box(value_to_param(&val_decimal)))
});
let val_array_ints = Value::Array(vec![Value::I64(1), Value::I64(2), Value::I64(3)]);
group.bench_function("array_ints_3", |b| {
b.iter(|| std::hint::black_box(value_to_param(&val_array_ints)))
});
let val_array_large: Value = Value::Array((0_i64..100).map(Value::I64).collect());
group.bench_function("array_ints_100", |b| {
b.iter(|| std::hint::black_box(value_to_param(&val_array_large)))
});
group.finish();
}
fn bench_batch_row_conversion(c: &mut Criterion) {
let mut group = c.benchmark_group("batch_row_conversion");
let rows: Vec<Vec<Value>> = (0..100_i64)
.map(|i| {
vec![
Value::I64(i),
Value::F64(std::f64::consts::PI * (i as f64)),
Value::Text(format!("row_{i}")),
Value::Blob(vec![(i & 0xff) as u8, ((i >> 8) & 0xff) as u8, 0xbe, 0xef]),
Value::Timestamp(1_700_000_000_000_000 + i * 1_000_000),
Value::Date(19_000 + i as i32),
Value::Json(format!(r#"{{"id":{i}}}"#)),
Value::Null,
]
})
.collect();
group.bench_function("100rows_8cols_to_param", |b| {
b.iter(|| {
for row in &rows {
for val in row {
std::hint::black_box(value_to_param(std::hint::black_box(val)));
}
}
})
});
let rows_scalar: Vec<Vec<Value>> = (0..100_i64)
.map(|i| vec![Value::I64(i), Value::F64(i as f64), Value::Bool(i % 2 == 0)])
.collect();
group.bench_function("100rows_3cols_scalar_only", |b| {
b.iter(|| {
for row in &rows_scalar {
for val in row {
std::hint::black_box(value_to_param(std::hint::black_box(val)));
}
}
})
});
group.finish();
}
fn bench_query_throughput(c: &mut Criterion) {
let url = match std::env::var("POSTGRES_URL") {
Ok(u) => u,
Err(_) => return,
};
let rt = tokio::runtime::Runtime::new().expect("rt");
let conn = rt
.block_on(PgConnection::connect(&url, TlsMode::Disabled))
.expect("connect");
rt.block_on(conn.execute_batch(
"DROP TABLE IF EXISTS bench_qt_b;
DROP TABLE IF EXISTS bench_qt_a;
CREATE TABLE bench_qt_a (id BIGINT PRIMARY KEY, val TEXT NOT NULL);
CREATE TABLE bench_qt_b (id BIGINT, a_id BIGINT);",
))
.expect("setup tables");
for i in 0_i64..100 {
rt.block_on(conn.execute(
"INSERT INTO bench_qt_a (id, val) VALUES ($1, $2)",
&[&i, &format!("val_{i}")],
))
.expect("insert a");
rt.block_on(conn.execute(
"INSERT INTO bench_qt_b (id, a_id) VALUES ($1, $2)",
&[&i, &i],
))
.expect("insert b");
}
let mut group = c.benchmark_group("query_throughput");
group.bench_function("simple_select", |b| {
b.to_async(&rt).iter(|| async {
std::hint::black_box(conn.query("SELECT 1", &[]).await.expect("simple_select"))
})
});
group.bench_function("parameterized_select", |b| {
b.to_async(&rt).iter(|| async {
std::hint::black_box(
conn.query("SELECT id, val FROM bench_qt_a WHERE id = $1", &[&42_i64])
.await
.expect("param_select"),
)
})
});
group.bench_function("join_query", |b| {
b.to_async(&rt).iter(|| async {
std::hint::black_box(
conn.query(
"SELECT a.id, a.val FROM bench_qt_a a \
JOIN bench_qt_b b ON a.id = b.a_id WHERE b.id = $1",
&[&42_i64],
)
.await
.expect("join_query"),
)
})
});
group.finish();
rt.block_on(
conn.execute_batch("DROP TABLE IF EXISTS bench_qt_b; DROP TABLE IF EXISTS bench_qt_a;"),
)
.expect("cleanup");
}
fn bench_pool_vs_single_connection(c: &mut Criterion) {
let url = match std::env::var("POSTGRES_URL") {
Ok(u) => u,
Err(_) => return,
};
let rt = tokio::runtime::Runtime::new().expect("rt");
let conns: Vec<PgConnection> = (0..10)
.map(|_| {
rt.block_on(PgConnection::connect(&url, TlsMode::Disabled))
.expect("pool conn")
})
.collect();
let single = conns[0].clone();
let mut group = c.benchmark_group("pool_vs_single_connection");
group.bench_function("pool_10conn", |b| {
b.to_async(&rt).iter(|| async {
let futs: Vec<_> = conns.iter().map(|c| c.query("SELECT 1", &[])).collect();
let results = futures::future::join_all(futs).await;
std::hint::black_box(results)
})
});
group.bench_function("single_conn", |b| {
b.to_async(&rt).iter(|| async {
let mut results = Vec::with_capacity(10);
for _ in 0..10 {
results.push(single.query("SELECT 1", &[]).await.expect("single q"));
}
std::hint::black_box(results)
})
});
group.finish();
}
fn bench_copy_vs_insert(c: &mut Criterion) {
let url = match std::env::var("POSTGRES_URL") {
Ok(u) => u,
Err(_) => return,
};
let rt = tokio::runtime::Runtime::new().expect("rt");
let conn = rt
.block_on(PgConnection::connect(&url, TlsMode::Disabled))
.expect("connect");
rt.block_on(conn.execute_batch(
"DROP TABLE IF EXISTS bench_copy_tgt;
CREATE TABLE bench_copy_tgt (id BIGINT, val TEXT);",
))
.expect("setup");
let rows: Vec<Vec<String>> = (0_i64..100)
.map(|i| vec![i.to_string(), format!("val_{i}")])
.collect();
let insert_sql: String = {
let values: Vec<String> = (0_i64..100).map(|i| format!("({i}, 'val_{i}')")).collect();
format!(
"INSERT INTO bench_copy_tgt (id, val) VALUES {}",
values.join(",")
)
};
let mut group = c.benchmark_group("copy_vs_insert");
group.bench_function("bulk_insert_100", |b| {
b.to_async(&rt).iter(|| async {
conn.execute_batch(&insert_sql).await.expect("bulk_insert");
conn.execute_batch("TRUNCATE bench_copy_tgt")
.await
.expect("truncate");
})
});
group.bench_function("copy_protocol_100", |b| {
b.to_async(&rt).iter(|| async {
let row_iter = rows.clone().into_iter();
std::hint::black_box(
conn.copy_in_text("bench_copy_tgt", &["id", "val"], row_iter)
.await
.expect("copy_in"),
);
conn.execute_batch("TRUNCATE bench_copy_tgt")
.await
.expect("truncate");
})
});
group.finish();
rt.block_on(conn.execute_batch("DROP TABLE IF EXISTS bench_copy_tgt;"))
.expect("cleanup");
}
fn bench_arc_mutex_contention(c: &mut Criterion) {
let url = match std::env::var("POSTGRES_URL") {
Ok(u) => u,
Err(_) => return,
};
let rt = tokio::runtime::Runtime::new().expect("rt");
let conn = rt
.block_on(PgConnection::connect(&url, TlsMode::Disabled))
.expect("connect");
let mut group = c.benchmark_group("arc_mutex_contention");
for n_tasks in [1_usize, 5, 20] {
group.bench_with_input(
BenchmarkId::new("concurrent_tasks", n_tasks),
&n_tasks,
|b, &n| {
b.to_async(&rt).iter(|| async {
let futs: Vec<_> = (0..n).map(|_| conn.query("SELECT 1", &[])).collect();
let results = futures::future::join_all(futs).await;
std::hint::black_box(results)
})
},
);
}
group.finish();
}
fn bench_prepared_stmt_cache(c: &mut Criterion) {
let url = match std::env::var("POSTGRES_URL") {
Ok(u) => u,
Err(_) => return,
};
let rt = tokio::runtime::Runtime::new().expect("rt");
let conn = rt
.block_on(PgConnection::connect(&url, TlsMode::Disabled))
.expect("connect");
rt.block_on(conn.execute_batch(
"DROP TABLE IF EXISTS bench_ps_cache;
CREATE TABLE bench_ps_cache (id BIGINT PRIMARY KEY);",
))
.expect("setup");
for i in 0_i64..50 {
rt.block_on(conn.execute("INSERT INTO bench_ps_cache (id) VALUES ($1)", &[&i]))
.expect("insert");
}
let _warmup = rt
.block_on(conn.prepare("SELECT id FROM bench_ps_cache WHERE id = $1"))
.expect("warmup prepare");
let mut group = c.benchmark_group("prepared_stmt_cache");
group.bench_function("with_prepare", |b| {
b.to_async(&rt).iter(|| async {
let mut stmt = conn
.prepare("SELECT id FROM bench_ps_cache WHERE id = $1")
.await
.expect("prepare hit");
std::hint::black_box(stmt.query(&[&42_i64]).await.expect("exec prepared"))
})
});
group.bench_function("without_prepare", |b| {
b.to_async(&rt).iter(|| async {
std::hint::black_box(
conn.query("SELECT id FROM bench_ps_cache WHERE id = $1", &[&42_i64])
.await
.expect("query unprepared"),
)
})
});
group.finish();
rt.block_on(conn.execute_batch("DROP TABLE IF EXISTS bench_ps_cache;"))
.expect("cleanup");
}
criterion_group!(
benches,
bench_value_to_param_int,
bench_value_to_param_text,
bench_value_to_param_float,
bench_value_to_param_null,
bench_value_to_param_bool,
bench_value_to_param_all_types,
bench_batch_row_conversion,
bench_query_throughput,
bench_pool_vs_single_connection,
bench_copy_vs_insert,
bench_arc_mutex_contention,
bench_prepared_stmt_cache,
);
criterion_main!(benches);