Skip to main content

diff_benchmark/
diff_benchmark.rs

1use 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}