use indexmap::IndexMap; struct Lookup { generated: Map, reference: IndexMap, keys: Vec, } struct Comparison { values: Vec, expected: Id, } fn id_fault() -> Fault { &FaultInfo { message: "integer arithmetic fault", source: "reference.dever", line: 1, column: 1, } } fn roundtrip_generated(values: &[String]) -> Result { GENERATED_ROUNDTRIP(values.first().map(String::as_str).unwrap_or("")) } fn roundtrip_reference(values: &[String]) -> Result { let id = Id::from(values.first().map(String::as_str).unwrap_or("").to_owned()); Ok(id.into_text()) } fn encode_reference(values: &[String]) -> Result, Fault> { Ok(values.iter().map(|text| Id::from(text.clone())).collect()) } fn compare_generated(values: &[Comparison]) -> Result { match values.first() { Some(value) => GENERATED_COMPARE(&value.values, &value.expected), None => Ok(0), } } fn compare_reference(values: &[Comparison]) -> Result { let Some(value) = values.first() else { return Ok(0); }; let expected = value.expected.clone(); let mut count = 0i64; for item in &value.values { count = count .checked_add(i64::from(item == &expected)) .ok_or_else(id_fault)?; } Ok(count) } fn lookup_generated(values: &[Lookup]) -> Result { match values.first() { Some(value) => GENERATED_LOOKUP(&value.generated, &value.keys), None => Ok(0), } } fn lookup_reference(values: &[Lookup]) -> Result { let Some(value) = values.first() else { return Ok(0); }; let mut count = 0i64; for key in &value.keys { count = count .checked_add(value.reference.get(key).copied().unwrap_or(0)) .ok_or_else(id_fault)?; } Ok(count) } fn lookup_same_table(values: &[Lookup]) -> Result { let Some(value) = values.first() else { return Ok(0); }; let mut count = 0i64; for key in &value.keys { count = count .checked_add(value.generated.get(key).copied().unwrap_or(0)) .ok_or_else(id_fault)?; } Ok(count) } fn map_reference(values: &[String]) -> Result, Fault> { let mut map = IndexMap::new(); for text in values { map.insert(Id::from(text.clone()), text.chars().count() as i64); } Ok(map.into_iter().collect()) } fn main() { let texts: Vec = (0..512) .map(|index| format!("user-{index:04}-雪")) .collect(); let short = ["user-0012-雪".to_owned()]; let long = ["雪山树林".repeat(512)]; let reference = || { texts .iter() .map(|text| (Id::from(text.clone()), text.chars().count() as i64)) .collect() }; // 建表顺序影响独立表的哈希与分配布局;复测须等量覆盖两种顺序。 let (generated, reference) = match std::env::var("DEVER_BENCH_MAP_ORDER") .as_deref() .unwrap_or("dever-first") { "dever-first" => { let generated = GENERATED_BUILD_MAP(&texts).unwrap(); (generated, reference()) } "rust-first" => { let reference = reference(); (GENERATED_BUILD_MAP(&texts).unwrap(), reference) } order => panic!("unknown table construction order: {order}"), }; let keys = (0..1024) .map(|index| Id::from(format!("user-{:04}-雪", (index * 17) % 576))) .collect(); let lookups = [Lookup { generated, reference, keys, }]; let comparisons = [Comparison { values: (0..1024) .map(|index| Id::from(format!("user-{:04}-雪", index % 32))) .collect(), expected: Id::from("user-0012-雪".to_owned()), }]; assert_eq!( GENERATED_ENTRIES(&["a".into(), "b".into(), "a".into()]).unwrap(), map_reference(&["a".into(), "b".into(), "a".into()]).unwrap() ); println!("case,sample,implementation,elapsed_ns,checksum"); compare( "id_roundtrip_short", &short, 300000, roundtrip_generated, roundtrip_reference, |value| value.len() as u64, ); compare( "id_roundtrip_long", &long, 20000, roundtrip_generated, roundtrip_reference, |value| value.len() as u64, ); compare( "id_each", &texts, 300, GENERATED_ENCODE_ALL, encode_reference, |value| value.len() as u64, ); compare( "id_compare", &comparisons, 2000, compare_generated, compare_reference, |value| *value as u64, ); compare( "id_map_lookup", &lookups, 1000, lookup_generated, lookup_reference, |value| *value as u64, ); // 双方读取同一张表,避免将各自随机哈希状态的差异归给生成代码。 compare_named( "id_map_lookup_same_table", &lookups, 1000, lookup_generated, lookup_same_table, |value| *value as u64, "rust_runtime", ); compare( "id_map_build", &texts, 300, GENERATED_ENTRIES, map_reference, |value| value.len() as u64, ); }