1use std::time::Instant;
10
11use crate::app::App;
12use crate::buffer::Buffer;
13use crate::fold::FoldState;
14use crate::syntax::SyntaxEngine;
15
16pub struct BenchResult {
17 pub name: String,
18 pub ms: f64,
19 pub detail: String,
20}
21
22impl BenchResult {
23 fn new(name: &str, ms: f64, detail: String) -> Self {
24 Self { name: name.to_string(), ms, detail }
25 }
26}
27
28pub struct BenchReport {
29 pub results: Vec<BenchResult>,
30 pub total_ms: f64,
31 pub synthetic_lines: usize,
33}
34
35fn time_ms(mut f: impl FnMut()) -> f64 {
36 let t = Instant::now();
37 f();
38 t.elapsed().as_secs_f64() * 1000.0
39}
40
41fn synthetic_rust(lines_target: usize) -> String {
43 let mut s = String::with_capacity(lines_target * 40);
44 let mut produced = 0usize;
45 let mut i = 0usize;
46 while produced < lines_target {
47 s.push_str(&format!("fn compute_{i}(x: usize) -> usize {{\n"));
48 s.push_str(" let mut total: usize = 0; // running sum\n");
49 s.push_str(" for step in 0..x {\n");
50 s.push_str(" total += step * 2 + 1;\n");
51 s.push_str(" if total > 100 { total -= 50; }\n");
52 s.push_str(" }\n");
53 s.push_str(" total\n");
54 s.push_str("}\n");
55 s.push('\n');
56 produced += 9;
57 i += 1;
58 }
59 s
60}
61
62fn mops(count: usize, ms: f64) -> f64 {
64 count as f64 / ms.max(1e-9) / 1000.0
65}
66
67fn mb_per_s(bytes: usize, ms: f64) -> f64 {
68 (bytes as f64 / 1_048_576.0) / (ms.max(1e-9) / 1000.0)
69}
70
71pub fn run(app: &App) -> BenchReport {
72 let mut results = Vec::new();
73
74 let src = synthetic_rust(4000);
75 let line_vec: Vec<String> = src.split('\n').map(|s| s.to_string()).collect();
76 let n_lines = line_vec.len();
77
78 let mut eng = SyntaxEngine::new();
80 let ms = time_ms(|| eng.parse(&src, Some("rs")));
81 let tokens = eng.tokens.len();
82 results.push(BenchResult::new(
83 "syntax parse (rust)",
84 ms,
85 format!("{n_lines} lines · {tokens} tokens · {:.0} klines/s", n_lines as f64 / ms.max(1e-9)),
86 ));
87
88 let mut folds = FoldState::new();
90 let ms = time_ms(|| folds.rebuild(&line_vec, 4));
91 let ranges = folds.ranges.len();
92 results.push(BenchResult::new(
93 "fold rebuild",
94 ms,
95 format!("{ranges} ranges · {:.0} klines/s", n_lines as f64 / ms.max(1e-9)),
96 ));
97
98 let iters = 1_000_000usize;
100 let mut hits = 0usize;
101 let ms = time_ms(|| {
102 for i in 0..iters {
103 let row = i.wrapping_mul(2654435761) % n_lines;
104 if folds.fold_at(row).is_some() {
105 hits += 1;
106 }
107 }
108 });
109 results.push(BenchResult::new(
110 "fold_at ×1M",
111 ms,
112 format!("{:.1} Mops/s ({hits} hits)", mops(iters, ms)),
113 ));
114
115 let reps = (1_000_000 / n_lines.max(1)).max(1);
117 let mut counted = 0usize;
118 let ms = time_ms(|| {
119 for _ in 0..reps {
120 for row in 0..n_lines {
121 counted += eng.tokens_for_row(row).len();
122 }
123 }
124 });
125 let calls = reps * n_lines;
126 results.push(BenchResult::new(
127 "tokens_for_row (render path)",
128 ms,
129 format!("{:.1} Mops/s ({calls} calls, {counted} toks)", mops(calls, ms)),
130 ));
131
132 let blob = "x".repeat(200_000);
134 let mut buf = Buffer::from_string("seed");
135 buf.cursor.col = 4;
136 let ms = time_ms(|| buf.insert_str(&blob));
137 results.push(BenchResult::new(
138 "insert_str 200KB paste",
139 ms,
140 format!("{:.0} MB/s", mb_per_s(blob.len(), ms)),
141 ));
142
143 let joined = Buffer::from_string(&src);
145 let ms = time_ms(|| {
146 let _ = joined.text();
147 });
148 results.push(BenchResult::new(
149 "buffer.text() join",
150 ms,
151 format!("{:.0} MB/s", mb_per_s(src.len(), ms)),
152 ));
153
154 let real = app.buffer.text();
156 if !real.trim().is_empty() {
157 let ext = app.file_extension();
158 let mut e2 = SyntaxEngine::new();
159 let rows = app.buffer.line_count();
160 let ms = time_ms(|| e2.parse(&real, ext.as_deref()));
161 let kb = real.len() / 1024;
162 let toks = e2.tokens.len();
163 results.push(BenchResult::new(
164 "your file: parse",
165 ms,
166 format!("{rows} lines · {kb} KB · {toks} tokens"),
167 ));
168 }
169
170 let total_ms = results.iter().map(|r| r.ms).sum();
171 BenchReport { results, total_ms, synthetic_lines: n_lines }
172}
173
174#[cfg(test)]
175mod tests {
176 use super::*;
177
178 #[test]
179 fn run_produces_results_without_panicking() {
180 let app = App::new();
181 let report = run(&app);
182 assert!(report.results.len() >= 6, "got {} results", report.results.len());
185 assert!(report.total_ms >= 0.0);
186 for r in &report.results {
187 assert!(r.ms >= 0.0, "{} had negative time", r.name);
188 assert!(!r.detail.is_empty());
189 }
190 }
191
192 #[test]
193 fn synthetic_source_folds_and_parses() {
194 let src = synthetic_rust(200);
195 assert!(src.lines().count() >= 200);
196 let lines: Vec<String> = src.split('\n').map(|s| s.to_string()).collect();
197 let mut folds = crate::fold::FoldState::new();
198 folds.rebuild(&lines, 4);
199 assert!(!folds.ranges.is_empty());
201 }
202}