278 lines
9.2 KiB
Rust
278 lines
9.2 KiB
Rust
// 两侧共享 SQLite、连接池、截止时间与 worker,单独测量 Dever 生成层的额外成本。
|
|
// 不把此比较当成未经资源约束的同步 rusqlite、HTTP 吞吐或整个 core 的验收。
|
|
use dever_runtime::{
|
|
database::{Context, Id as ModelId, Parameter, Registry},
|
|
task::{Limits, Runtime, Scope},
|
|
time::calendar::DateTime,
|
|
value::uuid::Uuid,
|
|
};
|
|
use std::{hint::black_box, path::PathBuf, sync::Arc, time::Instant};
|
|
|
|
struct Directory(PathBuf);
|
|
impl Drop for Directory {
|
|
fn drop(&mut self) {
|
|
std::fs::remove_dir_all(&self.0).unwrap();
|
|
}
|
|
}
|
|
|
|
async fn rust_count(context: &mut Context, scope: &Scope<'_>) -> i64 {
|
|
context
|
|
.on(0, scope)
|
|
.await
|
|
.unwrap()
|
|
.query(
|
|
scope,
|
|
"SELECT COUNT(*) FROM \"demo_account_model\"",
|
|
vec![],
|
|
1,
|
|
|row| Ok(row.get::<_, i64>(0)?),
|
|
)
|
|
.await
|
|
.unwrap()
|
|
.pop()
|
|
.unwrap()
|
|
}
|
|
|
|
struct RustAccount {
|
|
id: ModelId<0>,
|
|
created_at: DateTime,
|
|
name: String,
|
|
note: Option<String>,
|
|
uid: Uuid,
|
|
rank: i64,
|
|
}
|
|
|
|
async fn rust_row(
|
|
context: &mut Context,
|
|
scope: &Scope<'_>,
|
|
update: bool,
|
|
) -> Result<RustAccount, dever_runtime::database::Error> {
|
|
let (sql, parameters) = if update {
|
|
(
|
|
"UPDATE \"demo_account_model\" SET \"name\" = ?2 WHERE id = ?1 RETURNING \"id\",\"created_at\",\"name\",\"note\",\"uid\",\"rank\"",
|
|
vec![
|
|
Parameter::Integer(1),
|
|
Parameter::Text("benchmark 中文".into()),
|
|
],
|
|
)
|
|
} else {
|
|
(
|
|
"SELECT \"id\",\"created_at\",\"name\",\"note\",\"uid\",\"rank\" FROM \"demo_account_model\" WHERE id = ?1",
|
|
vec![Parameter::Integer(1)],
|
|
)
|
|
};
|
|
let sql = dever_runtime::database::Sql {
|
|
sqlite: sql,
|
|
postgres: if update {
|
|
"UPDATE \"demo_account_model\" SET \"name\" = $2 WHERE id = $1 RETURNING \"id\",\"created_at\",\"name\",\"note\",\"uid\",\"rank\""
|
|
} else {
|
|
"SELECT \"id\",\"created_at\",\"name\",\"note\",\"uid\",\"rank\" FROM \"demo_account_model\" WHERE id = $1"
|
|
},
|
|
};
|
|
context
|
|
.on(0, scope)
|
|
.await?
|
|
.query(scope, sql, parameters, 1, |row| {
|
|
Ok(RustAccount {
|
|
id: ModelId(row.get::<_, i64>(0)?),
|
|
created_at: DateTime::from_millis(row.get::<_, i64>(1)?).map_err(|_| {
|
|
dever_runtime::database::Error::new(
|
|
dever_runtime::database::Kind::InvalidData,
|
|
"invalid stored DateTime",
|
|
)
|
|
})?,
|
|
name: row.get::<_, String>(2)?,
|
|
note: row.get::<_, Option<String>>(3)?,
|
|
uid: dever_runtime::database::scalar::uuid(row, 4)?,
|
|
rank: row.get::<_, i64>(5)?,
|
|
})
|
|
})
|
|
.await?
|
|
.pop()
|
|
.ok_or_else(|| {
|
|
dever_runtime::database::Error::new(
|
|
dever_runtime::database::Kind::NotFound,
|
|
"account was not found",
|
|
)
|
|
})
|
|
}
|
|
|
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
|
enum Case {
|
|
Count,
|
|
Read,
|
|
Update,
|
|
}
|
|
|
|
fn measure(
|
|
runtime: &Runtime,
|
|
registry: &Arc<Registry>,
|
|
generated: bool,
|
|
case: Case,
|
|
count: usize,
|
|
) -> (u128, i64) {
|
|
let mut context = Context::new(registry.clone());
|
|
runtime
|
|
.run(async |scope| {
|
|
let mut checksum = 0i64;
|
|
let start = Instant::now();
|
|
for _ in 0..count {
|
|
let value = if case != Case::Count {
|
|
let owner = (case == Case::Update).then(|| context.enter());
|
|
let row = if generated {
|
|
let value = if case == Case::Update {
|
|
GENERATED_UPDATE(
|
|
dever_runtime::database::Id(1),
|
|
"benchmark 中文",
|
|
scope,
|
|
&mut context,
|
|
)
|
|
.await
|
|
.unwrap()
|
|
} else {
|
|
GENERATED_READ(dever_runtime::database::Id(1), scope, &mut context)
|
|
.await
|
|
.unwrap()
|
|
};
|
|
RustAccount {
|
|
id: value.field0,
|
|
created_at: value.field1,
|
|
name: value.field2,
|
|
note: value.field3,
|
|
uid: value.field4,
|
|
rank: value.field5,
|
|
}
|
|
} else {
|
|
rust_row(&mut context, scope, case == Case::Update)
|
|
.await
|
|
.unwrap()
|
|
};
|
|
if let Some(owner) = owner {
|
|
context.finish(scope, owner, true).await.unwrap();
|
|
}
|
|
// 两侧都把完整记录交给调用者,不在 worker 内提前丢字段。
|
|
let row = black_box(&row);
|
|
assert_eq!(row.name, "benchmark 中文");
|
|
assert!(row.note.is_none());
|
|
row.id.0 + row.name.len() as i64
|
|
} else if generated {
|
|
GENERATED_COUNT(scope, &mut context).await.unwrap()
|
|
} else {
|
|
rust_count(&mut context, scope).await
|
|
};
|
|
checksum += black_box(value);
|
|
}
|
|
(start.elapsed().as_nanos(), checksum)
|
|
})
|
|
.unwrap()
|
|
}
|
|
|
|
fn main() {
|
|
let unique = std::time::SystemTime::now()
|
|
.duration_since(std::time::UNIX_EPOCH)
|
|
.unwrap()
|
|
.as_nanos();
|
|
let relative = format!("database-benchmark-{}-{unique}", std::process::id());
|
|
let directory = Directory(
|
|
std::env::current_exe()
|
|
.unwrap()
|
|
.parent()
|
|
.unwrap()
|
|
.join(&relative),
|
|
);
|
|
std::fs::create_dir(&directory.0).unwrap();
|
|
let settings = directory.0.join("setting.json");
|
|
let runtime = Runtime::new(Limits {
|
|
threads: 1,
|
|
workers: 1,
|
|
tasks: 16,
|
|
})
|
|
.unwrap();
|
|
let schema = database_settings(&runtime, &directory.0, &settings, &relative);
|
|
let registry = runtime
|
|
.run(async |scope| database_registry(&settings, scope).await)
|
|
.unwrap()
|
|
.unwrap();
|
|
runtime
|
|
.run(async |scope| {
|
|
let mut context = Context::new(registry.clone());
|
|
let created = GENERATED_CREATE("benchmark 中文", scope, &mut context)
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(created.field0.0, 1);
|
|
assert_eq!(
|
|
GENERATED_COUNT(scope, &mut context).await.unwrap(),
|
|
rust_count(&mut context, scope).await
|
|
);
|
|
let expected = (
|
|
created.field0,
|
|
created.field1,
|
|
created.field2,
|
|
created.field3,
|
|
created.field4,
|
|
created.field5,
|
|
);
|
|
let read = GENERATED_READ(ModelId(1), scope, &mut context)
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(
|
|
(
|
|
read.field0,
|
|
read.field1,
|
|
read.field2,
|
|
read.field3,
|
|
read.field4,
|
|
read.field5
|
|
),
|
|
expected
|
|
);
|
|
let read = rust_row(&mut context, scope, false).await.unwrap();
|
|
assert_eq!(
|
|
(
|
|
read.id,
|
|
read.created_at,
|
|
read.name,
|
|
read.note,
|
|
read.uid,
|
|
read.rank
|
|
),
|
|
expected
|
|
);
|
|
})
|
|
.unwrap();
|
|
println!("case,sample,implementation,nanoseconds,checksum");
|
|
for (name, case) in [
|
|
("database_count", Case::Count),
|
|
("database_get", Case::Read),
|
|
("database_update_commit", Case::Update),
|
|
] {
|
|
if let Ok(selected) = std::env::var("DEVER_BENCH_CASES")
|
|
&& !selected.split(',').any(|item| item == name)
|
|
{
|
|
continue;
|
|
}
|
|
for generated in [true, false] {
|
|
black_box(measure(&runtime, ®istry, generated, case, 200));
|
|
}
|
|
for sample in 0..15 {
|
|
let mut order = [("dever", true), ("rust", false), ("control", true)];
|
|
order.rotate_left(sample % 3);
|
|
let mut expected = None;
|
|
for (implementation, generated) in order {
|
|
let (elapsed, checksum) = measure(&runtime, ®istry, generated, case, 2000);
|
|
if let Some(expected) = expected {
|
|
assert_eq!(checksum, expected);
|
|
}
|
|
expected = Some(checksum);
|
|
println!("{name},{sample},{implementation},{elapsed},{checksum}");
|
|
}
|
|
}
|
|
}
|
|
drop(registry);
|
|
if let Some(schema) = schema {
|
|
schema.close(&runtime);
|
|
}
|
|
drop(runtime);
|
|
drop(directory);
|
|
}
|