benchmark/
benchmark.rs

1//! Performance benchmarks for flag-rs
2//!
3//! Run with: cargo run --release --example benchmark
4
5use flag_rs::{CommandBuilder, CompletionResult, Flag, FlagType, FlagValue};
6use std::time::{Duration, Instant};
7
8/// Simple benchmark harness
9struct 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        // Warmup
27        for _ in 0..10 {
28            f();
29        }
30
31        // Actual measurement
32        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
49/// Creates a simple command structure for benchmarking
50fn 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
63/// Creates a complex nested command structure
64fn 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    // Add 50 subcommands
76    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        // Add 10 nested subcommands
84        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    // Test completion with descriptions
228    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); // This will fail validation
296    });
297    bench.report(duration);
298}
299
300fn bench_memory_optimizations() {
301    println!("\n=== Memory Optimization Benchmarks ===");
302
303    // Test string interning
304    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    // Test optimized completion
314    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}