diff_benchmark/
diff_benchmark.rs1use std::time::Instant;
2
3use zirium::{
4 dialect::DialectRegistry,
5 diff::{DiffLimits, DiffOptions, compare},
6 parser::ParsedFile,
7 semantic::{Document, LoweringMode, lower_with_dialect_registry},
8};
9
10fn lower(source: &str) -> Document {
11 let registry = DialectRegistry::baseline();
12 let parsed = ParsedFile::parse_with_registry(source.as_bytes(), registry).unwrap();
13 lower_with_dialect_registry(&parsed, LoweringMode::Strict, registry)
14 .document
15 .expect("generated workload must lower strictly")
16}
17
18fn run(size: usize, name: &str, before: String, after: String) {
19 let before_bytes = before.len();
20 let after_bytes = after.len();
21 let before = lower(&before);
22 let after = lower(&after);
23 let started = Instant::now();
24 let diff = compare(
25 &before,
26 &after,
27 DialectRegistry::baseline(),
28 DiffOptions::default(),
29 DiffLimits {
30 max_work: 100_000_000,
31 max_changes: 2_000_000,
32 },
33 )
34 .expect("generated workload must compare");
35 let elapsed = started.elapsed();
36 let statistics = diff.statistics();
37 println!(
38 "size={size} case={name} before_bytes={before_bytes} after_bytes={after_bytes} changes={} \
39 matched={} work={} ambiguous={} bounded_fallback={} elapsed_ms={:.3}",
40 diff.len(),
41 statistics.matched_operations,
42 statistics.work_units,
43 statistics.ambiguous_groups,
44 statistics.bounded_fallback_groups,
45 elapsed.as_secs_f64() * 1_000.0,
46 );
47}
48
49fn operations(count: usize, repeated: bool, insertion: Option<usize>) -> String {
50 let mut source = String::from("module {\n");
51 for index in 0..=count {
52 if insertion == Some(index) {
53 source.push_str(" \"test.inserted\"() : () -> ()\n");
54 }
55 if index == count {
56 break;
57 }
58 let name = if repeated {
59 "test.repeated".to_owned()
60 } else {
61 format!("test.op{index}")
62 };
63 source.push_str(&format!(" \"{name}\"() : () -> ()\n"));
64 }
65 source.push_str("}\n");
66 source
67}
68
69fn nested_functions(count: usize) -> String {
70 let mut source = String::from("module {\n");
71 for index in 0..count {
72 source.push_str(&format!(
73 " func.func @function{index}() {{\n \"test.body\"() : () -> ()\n func.return\n }}\n"
74 ));
75 }
76 source.push_str("}\n");
77 source
78}
79
80fn cfg_cycle(blocks: usize) -> String {
81 let mut source = String::from("module { func.func @cycle() {\n^entry:\n cf.br ^block0\n");
82 for index in 0..blocks {
83 let next = (index + 1) % blocks;
84 source.push_str(&format!(
85 "^block{index}:\n \"test.body{index}\"() : () -> ()\n cf.br ^block{next}\n"
86 ));
87 }
88 source.push_str("} }\n");
89 source
90}
91
92fn main() {
93 let mut sizes = std::env::args()
94 .skip(1)
95 .map(|value| value.parse().expect("sizes must be positive integers"))
96 .collect::<Vec<usize>>();
97 if sizes.is_empty() {
98 sizes.push(1_000);
99 }
100 assert!(sizes.iter().all(|size| *size > 0), "sizes must be positive");
101
102 for size in sizes {
103 let unchanged = operations(size, false, None);
104 run(size, "unchanged", unchanged.clone(), unchanged);
105 run(
106 size,
107 "one_insertion",
108 operations(size, false, None),
109 operations(size, false, Some(size / 2)),
110 );
111 run(
112 size,
113 "repeated",
114 operations(size, true, None),
115 operations(size, true, Some(size / 2)),
116 );
117 let nested = nested_functions(size / 10 + 1);
118 run(size, "nested_functions", nested.clone(), nested);
119 let cfg = cfg_cycle(size / 20 + 2);
120 run(size, "cfg_cycle", cfg.clone(), cfg);
121 let opaque = "x".repeat(size.saturating_mul(64));
122 let opaque = format!(r#""test.opaque"() {{data = #vendor.data<{opaque}>}} : () -> ()"#);
123 run(size, "large_opaque", opaque.clone(), opaque);
124 }
125}