1use flag_rs::{CommandBuilder, CompletionResult, Flag, FlagType, FlagValue};
6use std::time::{Duration, Instant};
7
8struct Benchmark {
10 name: String,
11 iterations: usize,
12}
13
14impl Benchmark {
15 fn new(name: &str, iterations: usize) -> Self {
16 Self {
17 name: name.to_string(),
18 iterations,
19 }
20 }
21
22 fn run<F>(&self, mut f: F) -> Duration
23 where
24 F: FnMut(),
25 {
26 for _ in 0..10 {
28 f();
29 }
30
31 let start = Instant::now();
33 for _ in 0..self.iterations {
34 f();
35 }
36 start.elapsed()
37 }
38
39 fn report(&self, duration: Duration) {
40 #[allow(clippy::cast_precision_loss)]
41 let per_iter = duration.as_nanos() as f64 / self.iterations as f64;
42 println!(
43 "{:<40} {:>10.0} ns/iter ({:>6} iterations)",
44 self.name, per_iter, self.iterations
45 );
46 }
47}
48
49fn create_simple_cli() -> flag_rs::Command {
51 CommandBuilder::new("bench")
52 .short("Benchmark CLI")
53 .flag(
54 Flag::new("verbose")
55 .short('v')
56 .value_type(FlagType::Bool)
57 .default(FlagValue::Bool(false)),
58 )
59 .flag(Flag::new("output").short('o').value_type(FlagType::String))
60 .build()
61}
62
63fn create_complex_cli() -> flag_rs::Command {
65 let mut root = CommandBuilder::new("complex")
66 .short("Complex CLI with many subcommands")
67 .flag(
68 Flag::new("config")
69 .short('c')
70 .value_type(FlagType::File)
71 .default(FlagValue::String("config.yaml".to_string())),
72 )
73 .build();
74
75 for i in 0..50 {
77 let mut sub = CommandBuilder::new(format!("sub{i:02}"))
78 .short(format!("Subcommand {i}"))
79 .flag(Flag::new("flag1").short('1').value_type(FlagType::String))
80 .flag(Flag::new("flag2").short('2').value_type(FlagType::Int))
81 .build();
82
83 for j in 0..10 {
85 sub.add_command(
86 CommandBuilder::new(format!("nested{j}"))
87 .short(format!("Nested command {j}"))
88 .flag(Flag::new("deep").value_type(FlagType::Bool))
89 .build(),
90 );
91 }
92
93 root.add_command(sub);
94 }
95
96 root
97}
98
99fn bench_command_creation() {
100 println!("\n=== Command Creation Benchmarks ===");
101
102 let bench = Benchmark::new("Simple command creation", 10_000);
103 let duration = bench.run(|| {
104 let _ = create_simple_cli();
105 });
106 bench.report(duration);
107
108 let bench = Benchmark::new("Complex command creation (50 subs)", 100);
109 let duration = bench.run(|| {
110 let _ = create_complex_cli();
111 });
112 bench.report(duration);
113
114 let bench = Benchmark::new("CommandBuilder with 10 flags", 1_000);
115 let duration = bench.run(|| {
116 let mut cmd = CommandBuilder::new("test");
117 for i in 0..10 {
118 cmd = cmd.flag(Flag::new(format!("flag{i}")).value_type(FlagType::String));
119 }
120 let _ = cmd.build();
121 });
122 bench.report(duration);
123}
124
125fn bench_flag_parsing() {
126 println!("\n=== Flag Parsing Benchmarks ===");
127
128 let cli = create_simple_cli();
129
130 let bench = Benchmark::new("Parse no flags", 10_000);
131 let duration = bench.run(|| {
132 let args = vec!["bench".to_string()];
133 let _ = cli.execute(args);
134 });
135 bench.report(duration);
136
137 let bench = Benchmark::new("Parse single flag", 10_000);
138 let duration = bench.run(|| {
139 let args = vec!["bench".to_string(), "--verbose".to_string()];
140 let _ = cli.execute(args);
141 });
142 bench.report(duration);
143
144 let bench = Benchmark::new("Parse flag with value", 10_000);
145 let duration = bench.run(|| {
146 let args = vec![
147 "bench".to_string(),
148 "--output".to_string(),
149 "file.txt".to_string(),
150 ];
151 let _ = cli.execute(args);
152 });
153 bench.report(duration);
154
155 let bench = Benchmark::new("Parse multiple flags", 10_000);
156 let duration = bench.run(|| {
157 let args = vec![
158 "bench".to_string(),
159 "-v".to_string(),
160 "-o".to_string(),
161 "output.txt".to_string(),
162 ];
163 let _ = cli.execute(args);
164 });
165 bench.report(duration);
166}
167
168fn bench_subcommand_lookup() {
169 println!("\n=== Subcommand Lookup Benchmarks ===");
170
171 let cli = create_complex_cli();
172
173 let bench = Benchmark::new("Find immediate subcommand", 10_000);
174 let duration = bench.run(|| {
175 let _ = cli.find_subcommand("sub25");
176 });
177 bench.report(duration);
178
179 let bench = Benchmark::new("Find nested subcommand", 10_000);
180 let duration = bench.run(|| {
181 if let Some(sub) = cli.find_subcommand("sub25") {
182 let _ = sub.find_subcommand("nested5");
183 }
184 });
185 bench.report(duration);
186
187 let bench = Benchmark::new("Execute nested command", 1_000);
188 let duration = bench.run(|| {
189 let args = vec![
190 "complex".to_string(),
191 "sub25".to_string(),
192 "nested5".to_string(),
193 "--deep".to_string(),
194 ];
195 let _ = cli.execute(args);
196 });
197 bench.report(duration);
198}
199
200fn bench_completion() {
201 println!("\n=== Completion Benchmarks ===");
202
203 let cli = CommandBuilder::new("comp")
204 .arg_completion(|_ctx, prefix| {
205 let items: Vec<String> = (0..100)
206 .map(|i| format!("item{i:03}"))
207 .filter(|item| item.starts_with(prefix))
208 .collect();
209 Ok(CompletionResult::new().extend(items))
210 })
211 .build();
212
213 let ctx = flag_rs::Context::new(vec!["comp".to_string()]);
214
215 let bench = Benchmark::new("Complete with empty prefix (100 items)", 1_000);
216 let duration = bench.run(|| {
217 let _ = cli.get_completions(&ctx, "", None);
218 });
219 bench.report(duration);
220
221 let bench = Benchmark::new("Complete with prefix (filtered)", 1_000);
222 let duration = bench.run(|| {
223 let _ = cli.get_completions(&ctx, "item05", None);
224 });
225 bench.report(duration);
226
227 let cli_desc = CommandBuilder::new("comp_desc")
229 .arg_completion(|_ctx, prefix| {
230 let mut result = CompletionResult::new();
231 for i in 0..50 {
232 let item = format!("option{i:02}");
233 if item.starts_with(prefix) {
234 result =
235 result.add_with_description(item, format!("Description for option {i}"));
236 }
237 }
238 Ok(result)
239 })
240 .build();
241
242 let bench = Benchmark::new("Complete with descriptions (50 items)", 1_000);
243 let duration = bench.run(|| {
244 let _ = cli_desc.get_completions(&ctx, "", None);
245 });
246 bench.report(duration);
247}
248
249fn bench_flag_validation() {
250 println!("\n=== Flag Validation Benchmarks ===");
251
252 let cli = CommandBuilder::new("validate")
253 .flag(Flag::new("choice").value_type(FlagType::Choice(vec![
254 "opt1".to_string(),
255 "opt2".to_string(),
256 "opt3".to_string(),
257 ])))
258 .flag(Flag::new("range").value_type(FlagType::Range(1, 100)))
259 .flag(
260 Flag::new("required")
261 .value_type(FlagType::String)
262 .required(),
263 )
264 .build();
265
266 let bench = Benchmark::new("Validate choice flag", 10_000);
267 let duration = bench.run(|| {
268 let args = vec![
269 "validate".to_string(),
270 "--choice".to_string(),
271 "opt2".to_string(),
272 "--required".to_string(),
273 "value".to_string(),
274 ];
275 let _ = cli.execute(args);
276 });
277 bench.report(duration);
278
279 let bench = Benchmark::new("Validate range flag", 10_000);
280 let duration = bench.run(|| {
281 let args = vec![
282 "validate".to_string(),
283 "--range".to_string(),
284 "50".to_string(),
285 "--required".to_string(),
286 "value".to_string(),
287 ];
288 let _ = cli.execute(args);
289 });
290 bench.report(duration);
291
292 let bench = Benchmark::new("Validate missing required flag", 10_000);
293 let duration = bench.run(|| {
294 let args = vec!["validate".to_string()];
295 let _ = cli.execute(args); });
297 bench.report(duration);
298}
299
300fn bench_memory_optimizations() {
301 println!("\n=== Memory Optimization Benchmarks ===");
302
303 let bench = Benchmark::new("String interning (100 strings)", 1_000);
305 let duration = bench.run(|| {
306 use flag_rs::string_pool;
307 for i in 0..100 {
308 let _ = string_pool::intern(&format!("flag{i}"));
309 }
310 });
311 bench.report(duration);
312
313 let bench = Benchmark::new("Optimized completion result", 1_000);
315 let duration = bench.run(|| {
316 use flag_rs::completion_optimized::CompletionResultOptimized;
317 use std::borrow::Cow;
318
319 let mut result = CompletionResultOptimized::new();
320 for i in 0..50 {
321 result = result.add_with_description(
322 Cow::Owned(format!("item{i}")),
323 Cow::Borrowed("Static description"),
324 );
325 }
326 let _ = result.into_legacy();
327 });
328 bench.report(duration);
329}
330
331fn main() {
332 println!("=== Flag-rs Performance Benchmarks ===");
333 println!("Running benchmarks... (this may take a minute)\n");
334
335 let total_start = Instant::now();
336
337 bench_command_creation();
338 bench_flag_parsing();
339 bench_subcommand_lookup();
340 bench_completion();
341 bench_flag_validation();
342 bench_memory_optimizations();
343
344 let total_duration = total_start.elapsed();
345 println!("\n=== Summary ===");
346 println!("Total benchmark time: {:.2?}", total_duration);
347
348 println!("\n=== Performance Characteristics ===");
349 println!("• Command creation: O(n) where n = number of flags/subcommands");
350 println!("• Flag parsing: O(n) where n = number of arguments");
351 println!("• Subcommand lookup: O(1) average (HashMap)");
352 println!("• Completion: O(n*m) where n = items, m = prefix length");
353 println!("• Memory usage: Optimized with string interning and Cow strings");
354
355 println!("\n=== Comparison Notes ===");
356 println!("While we don't have direct clap benchmarks (zero dependencies),");
357 println!("flag-rs focuses on:");
358 println!("• Dynamic completions (unique feature)");
359 println!("• Memory efficiency for large CLIs");
360 println!("• Fast subcommand lookup with HashMaps");
361 println!("• Minimal allocations during parsing");
362 println!("\nSee PERFORMANCE.md for detailed analysis.");
363}