perf: Add simple throughput benchmark

This adds a simple throughput benchmark for rusqlite and Turso, allowing
to compare the two, but also MVCC and SQLite transactions.
This commit is contained in:
Pekka Enberg
2025-09-11 16:12:51 +03:00
parent f9f7a44955
commit 964dd0cd43
7 changed files with 2707 additions and 1 deletions

View File

@@ -31,7 +31,11 @@ members = [
"sql_generation",
"whopper",
]
exclude = ["perf/latency/limbo"]
exclude = [
"perf/latency/limbo",
"perf/throughput/rusqlite",
"perf/throughput/turso"
]
[workspace.package]
version = "0.2.0-pre.1"

403
perf/throughput/rusqlite/Cargo.lock generated Normal file
View File

@@ -0,0 +1,403 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "ahash"
version = "0.8.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
dependencies = [
"cfg-if",
"once_cell",
"version_check",
"zerocopy",
]
[[package]]
name = "anstream"
version = "0.6.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd"
[[package]]
name = "anstyle-parse"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2"
dependencies = [
"windows-sys",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a"
dependencies = [
"anstyle",
"once_cell_polyfill",
"windows-sys",
]
[[package]]
name = "bitflags"
version = "2.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394"
[[package]]
name = "cc"
version = "1.2.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5252b3d2648e5eedbc1a6f501e3c795e07025c1e93bbf8bbdd6eef7f447a6d54"
dependencies = [
"find-msvc-tools",
"shlex",
]
[[package]]
name = "cfg-if"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
[[package]]
name = "clap"
version = "4.5.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7eac00902d9d136acd712710d71823fb8ac8004ca445a89e73a41d45aa712931"
dependencies = [
"clap_builder",
"clap_derive",
]
[[package]]
name = "clap_builder"
version = "4.5.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ad9bbf750e73b5884fb8a211a9424a1906c1e156724260fdae972f31d70e1d6"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_derive"
version = "4.5.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbfd7eae0b0f1a6e63d4b13c9c478de77c2eb546fba158ad50b4203dc24b9f9c"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap_lex"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
[[package]]
name = "colorchoice"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
[[package]]
name = "fallible-iterator"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
[[package]]
name = "fallible-streaming-iterator"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
[[package]]
name = "find-msvc-tools"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fd99930f64d146689264c637b5af2f0233a933bef0d8570e2526bf9e083192d"
[[package]]
name = "hashbrown"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
dependencies = [
"ahash",
]
[[package]]
name = "hashlink"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af"
dependencies = [
"hashbrown",
]
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "libsqlite3-sys"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c10584274047cb335c23d3e61bcef8e323adae7c5c8c760540f73610177fc3f"
dependencies = [
"cc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "once_cell"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "once_cell_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad"
[[package]]
name = "pkg-config"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]]
name = "proc-macro2"
version = "1.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rusqlite"
version = "0.31.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b838eba278d213a8beaf485bd313fd580ca4505a00d5871caeb1457c55322cae"
dependencies = [
"bitflags",
"fallible-iterator",
"fallible-streaming-iterator",
"hashlink",
"libsqlite3-sys",
"smallvec",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "smallvec"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "syn"
version = "2.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d"
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "windows-link"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
[[package]]
name = "windows-sys"
version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.53.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91"
dependencies = [
"windows-link",
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
[[package]]
name = "windows_aarch64_msvc"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
[[package]]
name = "windows_i686_gnu"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3"
[[package]]
name = "windows_i686_gnullvm"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
[[package]]
name = "windows_i686_msvc"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
[[package]]
name = "windows_x86_64_gnu"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57"
[[package]]
name = "windows_x86_64_msvc"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
[[package]]
name = "write-throughput"
version = "0.1.0"
dependencies = [
"clap",
"rusqlite",
]
[[package]]
name = "zerocopy"
version = "0.8.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831"
dependencies = [
"proc-macro2",
"quote",
"syn",
]

View File

@@ -0,0 +1,12 @@
[package]
name = "write-throughput"
version = "0.1.0"
edition = "2021"
[[bin]]
name = "write-throughput"
path = "src/main.rs"
[dependencies]
rusqlite = { version = "0.31", features = ["bundled"] }
clap = { version = "4.0", features = ["derive"] }

View File

@@ -0,0 +1,157 @@
use clap::Parser;
use rusqlite::{Connection, Result};
use std::sync::{Arc, Barrier};
use std::thread;
use std::time::Instant;
#[derive(Parser)]
#[command(name = "write-throughput")]
#[command(about = "Write throughput benchmark using rusqlite")]
struct Args {
#[arg(short = 't', long = "threads", default_value = "1")]
threads: usize,
#[arg(short = 'b', long = "batch-size", default_value = "100")]
batch_size: usize,
#[arg(short = 'i', long = "iterations", default_value = "10")]
iterations: usize,
}
fn main() -> Result<()> {
let args = Args::parse();
println!(
"Running write throughput benchmark with {} threads, {} batch size, {} iterations",
args.threads, args.batch_size, args.iterations
);
let db_path = "write_throughput_test.db";
if std::path::Path::new(db_path).exists() {
std::fs::remove_file(db_path).expect("Failed to remove existing database");
}
let wal_path = "write_throughput_test.db-wal";
if std::path::Path::new(wal_path).exists() {
std::fs::remove_file(wal_path).expect("Failed to remove existing database");
}
let _conn = setup_database(db_path)?;
let start_barrier = Arc::new(Barrier::new(args.threads));
let mut handles = Vec::new();
let overall_start = Instant::now();
for thread_id in 0..args.threads {
let db_path = db_path.to_string();
let barrier = Arc::clone(&start_barrier);
let handle = thread::spawn(move || {
worker_thread(
thread_id,
db_path,
args.batch_size,
args.iterations,
barrier,
)
});
handles.push(handle);
}
let mut total_inserts = 0;
for handle in handles {
match handle.join() {
Ok(Ok(inserts)) => total_inserts += inserts,
Ok(Err(e)) => {
eprintln!("Thread error: {}", e);
return Err(e);
}
Err(_) => {
eprintln!("Thread panicked");
std::process::exit(1);
}
}
}
let overall_elapsed = overall_start.elapsed();
let overall_throughput = (total_inserts as f64) / overall_elapsed.as_secs_f64();
println!("\n=== BENCHMARK RESULTS ===");
println!("Total inserts: {}", total_inserts);
println!("Total time: {:.2}s", overall_elapsed.as_secs_f64());
println!("Overall throughput: {:.2} inserts/sec", overall_throughput);
println!("Threads: {}", args.threads);
println!("Batch size: {}", args.batch_size);
println!("Iterations per thread: {}", args.iterations);
println!(
"Database file exists: {}",
std::path::Path::new(db_path).exists()
);
Ok(())
}
fn setup_database(db_path: &str) -> Result<Connection> {
let conn = Connection::open(db_path)?;
conn.pragma_update(None, "journal_mode", "WAL")?;
conn.pragma_update(None, "synchronous", "FULL")?;
conn.execute(
"CREATE TABLE IF NOT EXISTS test_table (
id INTEGER PRIMARY KEY,
data TEXT NOT NULL
)",
[],
)?;
println!("Database created at: {}", db_path);
Ok(conn)
}
fn worker_thread(
thread_id: usize,
db_path: String,
batch_size: usize,
iterations: usize,
start_barrier: Arc<Barrier>,
) -> Result<u64> {
let conn = Connection::open(&db_path)?;
conn.busy_timeout(std::time::Duration::from_secs(30))?;
let mut stmt = conn.prepare("INSERT INTO test_table (id, data) VALUES (?, ?)")?;
start_barrier.wait();
let start_time = Instant::now();
let mut total_inserts = 0;
for iteration in 0..iterations {
conn.execute("BEGIN", [])?;
for i in 0..batch_size {
let id = thread_id * iterations * batch_size + iteration * batch_size + i;
stmt.execute([&id.to_string(), &format!("data_{}", id)])?;
total_inserts += 1;
}
conn.execute("COMMIT", [])?;
}
let elapsed = start_time.elapsed();
let throughput = (total_inserts as f64) / elapsed.as_secs_f64();
println!(
"Thread {}: {} inserts in {:.2}s ({:.2} inserts/sec)",
thread_id,
total_inserts,
elapsed.as_secs_f64(),
throughput
);
Ok(total_inserts)
}

1931
perf/throughput/turso/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,13 @@
[package]
name = "write-throughput"
version = "0.1.0"
edition = "2021"
[[bin]]
name = "write-throughput"
path = "src/main.rs"
[dependencies]
turso = { path = "../../../bindings/rust" }
clap = { version = "4.0", features = ["derive"] }
tokio = { version = "1.0", features = ["full"] }

View File

@@ -0,0 +1,186 @@
use clap::{Parser, ValueEnum};
use std::sync::{Arc, Barrier};
use std::time::Instant;
use tokio::runtime::Runtime;
use turso::{Builder, Database, Result};
#[derive(Debug, Clone, Copy, ValueEnum)]
enum TransactionMode {
Legacy,
Mvcc,
Concurrent,
}
#[derive(Parser)]
#[command(name = "write-throughput")]
#[command(about = "Write throughput benchmark using turso")]
struct Args {
#[arg(short = 't', long = "threads", default_value = "1")]
threads: usize,
#[arg(short = 'b', long = "batch-size", default_value = "100")]
batch_size: usize,
#[arg(short = 'i', long = "iterations", default_value = "10")]
iterations: usize,
#[arg(short = 'm', long = "mode", default_value = "legacy")]
mode: TransactionMode,
}
#[tokio::main]
async fn main() -> Result<()> {
let args = Args::parse();
println!(
"Running write throughput benchmark with {} threads, {} batch size, {} iterations, mode: {:?}",
args.threads, args.batch_size, args.iterations, args.mode
);
let db_path = "write_throughput_test.db";
if std::path::Path::new(db_path).exists() {
std::fs::remove_file(db_path).expect("Failed to remove existing database");
}
let wal_path = "write_throughput_test.db-wal";
if std::path::Path::new(wal_path).exists() {
std::fs::remove_file(wal_path).expect("Failed to remove existing database");
}
let db = setup_database(db_path, args.mode).await?;
let start_barrier = Arc::new(Barrier::new(args.threads));
let mut handles = Vec::new();
let overall_start = Instant::now();
for thread_id in 0..args.threads {
let db_clone = db.clone();
let barrier = Arc::clone(&start_barrier);
let handle = tokio::task::spawn_blocking(move || {
let rt = Runtime::new().unwrap();
rt.block_on(worker_thread(
thread_id,
db_clone,
args.batch_size,
args.iterations,
barrier,
args.mode,
))
});
handles.push(handle);
}
let mut total_inserts = 0;
for handle in handles {
match handle.await {
Ok(Ok(inserts)) => total_inserts += inserts,
Ok(Err(e)) => {
eprintln!("Thread error: {}", e);
return Err(e);
}
Err(_) => {
eprintln!("Thread panicked");
std::process::exit(1);
}
}
}
let overall_elapsed = overall_start.elapsed();
let overall_throughput = (total_inserts as f64) / overall_elapsed.as_secs_f64();
println!("\n=== BENCHMARK RESULTS ===");
println!("Total inserts: {}", total_inserts);
println!("Total time: {:.2}s", overall_elapsed.as_secs_f64());
println!("Overall throughput: {:.2} inserts/sec", overall_throughput);
println!("Threads: {}", args.threads);
println!("Batch size: {}", args.batch_size);
println!("Iterations per thread: {}", args.iterations);
println!(
"Database file exists: {}",
std::path::Path::new(db_path).exists()
);
if let Ok(metadata) = std::fs::metadata(db_path) {
println!("Database file size: {} bytes", metadata.len());
}
Ok(())
}
async fn setup_database(db_path: &str, mode: TransactionMode) -> Result<Database> {
let builder = Builder::new_local(db_path);
let db = match mode {
TransactionMode::Legacy => builder.build().await?,
TransactionMode::Mvcc | TransactionMode::Concurrent => {
builder.with_mvcc(true).build().await?
}
};
let conn = db.connect()?;
conn.execute(
"CREATE TABLE IF NOT EXISTS test_table (
id INTEGER PRIMARY KEY,
data TEXT NOT NULL
)",
(),
)
.await?;
println!("Database created at: {}", db_path);
Ok(db)
}
async fn worker_thread(
thread_id: usize,
db: Database,
batch_size: usize,
iterations: usize,
start_barrier: Arc<Barrier>,
mode: TransactionMode,
) -> Result<u64> {
let conn = db.connect()?;
let mut stmt = conn
.prepare("INSERT INTO test_table (id, data) VALUES (?, ?)")
.await?;
start_barrier.wait();
let start_time = Instant::now();
let mut total_inserts = 0;
for iteration in 0..iterations {
let begin_stmt = match mode {
TransactionMode::Legacy | TransactionMode::Mvcc => "BEGIN",
TransactionMode::Concurrent => "BEGIN CONCURRENT",
};
conn.execute(begin_stmt, ()).await?;
for i in 0..batch_size {
let id = thread_id * iterations * batch_size + iteration * batch_size + i;
stmt.execute(turso::params::Params::Positional(vec![
turso::Value::Integer(id as i64),
turso::Value::Text(format!("data_{}", id)),
]))
.await?;
total_inserts += 1;
}
conn.execute("COMMIT", ()).await?;
}
let elapsed = start_time.elapsed();
let throughput = (total_inserts as f64) / elapsed.as_secs_f64();
println!(
"Thread {}: {} inserts in {:.2}s ({:.2} inserts/sec)",
thread_id,
total_inserts,
elapsed.as_secs_f64(),
throughput
);
Ok(total_inserts)
}