// 两侧共享 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, uid: Uuid, rank: i64, } async fn rust_row( context: &mut Context, scope: &Scope<'_>, update: bool, ) -> Result { 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>(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, 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); }