sweet_cli/bench/
bench_assert.rs

1use anyhow::Result;
2use clap::Parser;
3use std::fs;
4use std::process::Command;
5
6/// Measure the compilation time for the assert! macro
7///
8/// For context of an average large project:
9///
10/// `egrep -r "assert[!_]" . | wc -l`
11///
12/// bevy: 7,000
13/// wasm-bindgen: 3,000
14/// rust: 50,000
15///
16/// ## Expect
17/// 10 lines of 'expect' comilied in 0.53s, each line added 53.00ms
18/// 100 lines of 'expect' comilied in 0.47s, each line added 4.70ms
19/// 1000 lines of 'expect' comilied in 0.49s, each line added 0.49ms
20/// 2000 lines of 'expect' comilied in 0.50s, each line added 0.25ms
21/// 3000 lines of 'expect' comilied in 0.53s, each line added 0.18ms
22/// 5000 lines of 'expect' comilied in 0.56s, each line added 0.11ms
23/// 10000 lines of 'expect' comilied in 0.70s, each line added 0.07ms * consistency starts here
24/// 100000 lines of 'expect' comilied in 5.37s, each line added 0.05ms
25/// 20000 lines of 'expect' comilied in 1.06s, each line added 0.05ms
26/// 500000 lines of 'expect' comilied in 44.00s, each line added 0.09ms
27///
28/// ## Assert
29///
30/// 10 lines of 'assert' comilied in 0.21s, each line added 21.00ms
31/// 100 lines of 'assert' comilied in 0.23s, each line added 2.30ms
32/// 1000 lines of 'assert' comilied in 1.54s, each line added 1.54ms * smallest
33/// 2000 lines of 'assert' comilied in 4.92s, each line added 2.46ms
34/// 3000 lines of 'assert' comilied in 11.61s, each line added 3.87ms
35/// 5000 lines of 'assert' comilied in 26.96s, each line added 5.39ms * consistency starts here
36/// 10000 lines of 'assert' comilied in 55.00s, each line added 5.50ms
37/// 20000 lines of 'expect' comilied in 1.06s, each line added 0.05ms * this is incorrect, it actually took 10 mins
38/// 100000... no way dude
39#[derive(Debug, Parser)]
40pub struct BenchAssert {
41	#[arg(long, default_value_t = 1000)] // 1000 is the most gracious
42	iterations: usize,
43	#[arg(long)]
44	expect_only: bool,
45	#[arg(long)]
46	assert_only: bool,
47	/// no detectable difference
48	#[arg(long)]
49	release: bool,
50	/// no detectable difference
51	#[arg(long)]
52	run: bool,
53}
54
55const BENCH_DIR: &str = "./tests";
56
57impl BenchAssert {
58	pub fn run(self) -> Result<()> {
59		fs::create_dir_all(BENCH_DIR)?;
60
61		if self.expect_only {
62			self.run_expect()?;
63		} else if self.assert_only {
64			self.run_assert()?;
65		} else {
66			self.run_expect()?;
67			self.run_assert()?;
68		}
69
70		Ok(())
71	}
72
73
74	fn run_assert(&self) -> Result<()> {
75		self.create_iter_file(ASSERT_FILE_PATH, ASSERT_TEMPLATE, |i| {
76			format!("\tassert_eq!({},{});\n", i, i)
77		})?;
78		self.bench_compile("assert")?;
79		if self.run {
80			self.bench_run("assert")?;
81		}
82		Ok(())
83	}
84	fn run_expect(&self) -> Result<()> {
85		self.create_iter_file(EXPECT_FILE_PATH, EXPECT_TEMPLATE, |i| {
86			format!("\texpect({},{});\n", i, i)
87		})?;
88		self.bench_compile("expect")?;
89		if self.run {
90			self.bench_run("expect")?;
91		}
92		Ok(())
93	}
94
95	fn create_iter_file(
96		&self,
97		file_path: &str,
98		file_template: &str,
99		mk_str: impl Fn(usize) -> String,
100	) -> Result<()> {
101		let mut iterations = String::new();
102		for i in 0..self.iterations {
103			iterations.push_str(&mk_str(i));
104		}
105
106		let output =
107			String::from(file_template).replace("__iterations__", &iterations);
108
109		fs::write(file_path, output)?;
110		Ok(())
111	}
112
113
114	fn bench_compile(&self, test_name: &str) -> Result<()> {
115		// let path = path::Path::new(BENCH_DIR).join(test_name);
116		let mut command = Command::new("cargo");
117		command.arg("build").arg("--test").arg(test_name);
118		if self.release {
119			command.arg("--release");
120		}
121		let output = command.output()?;
122
123		let stderr = String::from_utf8_lossy(&output.stderr);
124
125		let duration = stderr
126			.lines()
127			.find(|line| line.contains("Finished"))
128			.expect("line not found")
129			.split(" ")
130			.last()
131			.unwrap()
132			.replace("s", "")
133			.parse::<f64>()
134			.unwrap();
135
136		let time_per_iter = (duration / self.iterations as f64) * 1000.;
137
138		println!(
139			"{} lines of '{}' comilied in {:.2}s, each line added {:.2}ms",
140			self.iterations, test_name, duration, time_per_iter
141		);
142		Ok(())
143	}
144
145	fn bench_run(&self, test_name: &str) -> Result<()> {
146		let output = Command::new("cargo")
147			.arg("test")
148			.arg("--test")
149			.arg(test_name)
150			.arg("--")
151			.arg("--nocapture")
152			.output()?;
153		let output = String::from_utf8_lossy(&output.stdout);
154		println!("{}", output);
155
156		let duration = output
157			.lines()
158			.find(|line| line.contains("__"))
159			.and_then(|line| line.split("__").nth(1))
160			.and_then(|num| num.parse::<f64>().ok())
161			.expect("Failed to find and parse number");
162
163		let time_per_iter = (duration / self.iterations as f64) * 1000.;
164
165		println!(
166			"{} lines of '{}' ran in {:.2}s, each line added {:.2}ms",
167			self.iterations, test_name, duration, time_per_iter
168		);
169
170		Ok(())
171	}
172}
173
174const ASSERT_FILE_PATH: &str = "./tests/assert.rs";
175const ASSERT_TEMPLATE: &str = r#"
176	use std::time::Instant;
177	#[test]	
178	fn main(){
179  	let start = Instant::now();
180__iterations__
181		println!("__{:.2}__", start.elapsed().as_secs_f32());
182}
183"#;
184const EXPECT_FILE_PATH: &str = "./tests/expect.rs";
185const EXPECT_TEMPLATE: &str = r#"
186	use std::time::Instant;
187	#[test]
188	fn main(){
189  	let start = Instant::now();
190__iterations__
191		println!("__{:.2}__", start.elapsed().as_secs_f32());
192	}
193
194	fn expect(a: i32, b: i32) {
195		if a != b {
196			panic!("Expected {} but got {}", a, b);
197		}
198	}
199"#;