|
| 1 | +use std::{fmt::Write, fs, time::Instant}; |
| 2 | + |
| 3 | +const ITERATIONS: u32 = 1_000_000; |
| 4 | + |
| 5 | +fn generate_svg(results: &[BenchResult]) -> String { |
| 6 | + let max_ns = results |
| 7 | + .iter() |
| 8 | + .flat_map(|r| [r.format_ns, r.formatx_ns]) |
| 9 | + .fold(0.0_f64, f64::max); |
| 10 | + |
| 11 | + let chart_h = 240; |
| 12 | + let top = 90; |
| 13 | + let bottom = top + chart_h; |
| 14 | + let group_w: usize = 140; |
| 15 | + let bar_w: usize = 45; |
| 16 | + let gap: usize = 8; |
| 17 | + let left: usize = 70; |
| 18 | + let total_w = left + group_w * results.len() + 20; |
| 19 | + let total_h = bottom + 60; |
| 20 | + |
| 21 | + let mut svg = String::new(); |
| 22 | + let _ = writeln!( |
| 23 | + svg, |
| 24 | + "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 {total_w} {total_h}\" font-family=\"'Segoe UI', system-ui, sans-serif\">" |
| 25 | + ); |
| 26 | + let _ = writeln!( |
| 27 | + svg, |
| 28 | + "<rect width=\"{total_w}\" height=\"{total_h}\" fill=\"#1e293b\"/>" |
| 29 | + ); |
| 30 | + |
| 31 | + // Title |
| 32 | + let cx = total_w / 2; |
| 33 | + let _ = writeln!( |
| 34 | + svg, |
| 35 | + "<text x=\"{cx}\" y=\"28\" text-anchor=\"middle\" fill=\"#f1f5f9\" font-size=\"16\" font-weight=\"700\">formatx! vs format!</text>" |
| 36 | + ); |
| 37 | + let _ = writeln!( |
| 38 | + svg, |
| 39 | + "<text x=\"{cx}\" y=\"44\" text-anchor=\"middle\" fill=\"#94a3b8\" font-size=\"11\">{ITERATIONS} iterations \u{00b7} release mode \u{00b7} lower is better</text>" |
| 40 | + ); |
| 41 | + |
| 42 | + // Legend |
| 43 | + let lx = cx - 70; |
| 44 | + let _ = writeln!( |
| 45 | + svg, |
| 46 | + "<rect x=\"{lx}\" y=\"52\" width=\"12\" height=\"12\" fill=\"#818cf8\"/><text x=\"{}\" y=\"63\" fill=\"#c7d2fe\" font-size=\"11\">format!</text>", |
| 47 | + lx + 17 |
| 48 | + ); |
| 49 | + let rx = cx + 20; |
| 50 | + let _ = writeln!( |
| 51 | + svg, |
| 52 | + "<rect x=\"{rx}\" y=\"52\" width=\"12\" height=\"12\" fill=\"#fb923c\"/><text x=\"{}\" y=\"63\" fill=\"#fed7aa\" font-size=\"11\">formatx!</text>", |
| 53 | + rx + 17 |
| 54 | + ); |
| 55 | + |
| 56 | + // Y-axis label |
| 57 | + let mid_y = top + chart_h / 2; |
| 58 | + let _ = writeln!( |
| 59 | + svg, |
| 60 | + "<text x=\"14\" y=\"{mid_y}\" text-anchor=\"middle\" fill=\"#64748b\" font-size=\"10\" transform=\"rotate(-90, 14, {mid_y})\">(ns/iter)</text>" |
| 61 | + ); |
| 62 | + |
| 63 | + // Y-axis gridlines |
| 64 | + for i in 0..=4 { |
| 65 | + let scale = max_ns * 1.15; |
| 66 | + let ns = scale * i as f64 / 4.0; |
| 67 | + let y = bottom - (chart_h as f64 * i as f64 / 4.0) as usize; |
| 68 | + let _ = writeln!( |
| 69 | + svg, |
| 70 | + "<line x1=\"{left}\" y1=\"{y}\" x2=\"{}\" y2=\"{y}\" stroke=\"#334155\" stroke-width=\"1\"/>", |
| 71 | + left + group_w * results.len() |
| 72 | + ); |
| 73 | + let _ = writeln!( |
| 74 | + svg, |
| 75 | + "<text x=\"{}\" y=\"{}\" text-anchor=\"end\" fill=\"#64748b\" font-size=\"10\">{:.0}</text>", |
| 76 | + left - 6, |
| 77 | + y + 4, |
| 78 | + ns |
| 79 | + ); |
| 80 | + } |
| 81 | + |
| 82 | + // Bars |
| 83 | + for (i, r) in results.iter().enumerate() { |
| 84 | + let gx = left + i * group_w + (group_w - bar_w * 2 - gap) / 2; |
| 85 | + |
| 86 | + let scale = max_ns * 1.15; |
| 87 | + let h1 = (r.format_ns / scale * chart_h as f64).max(2.0) as usize; |
| 88 | + let h2 = (r.formatx_ns / scale * chart_h as f64).max(2.0) as usize; |
| 89 | + |
| 90 | + let x1 = gx; |
| 91 | + let x2 = gx + bar_w + gap; |
| 92 | + let y1 = bottom - h1; |
| 93 | + let y2 = bottom - h2; |
| 94 | + |
| 95 | + // format! bar + label |
| 96 | + let _ = writeln!( |
| 97 | + svg, |
| 98 | + "<rect x=\"{x1}\" y=\"{y1}\" width=\"{bar_w}\" height=\"{h1}\" fill=\"#818cf8\"/>" |
| 99 | + ); |
| 100 | + let _ = writeln!( |
| 101 | + svg, |
| 102 | + "<text x=\"{}\" y=\"{}\" text-anchor=\"middle\" fill=\"#c7d2fe\" font-size=\"10\">{:.0} ns</text>", |
| 103 | + x1 + bar_w / 2, |
| 104 | + y1 - 6, |
| 105 | + r.format_ns |
| 106 | + ); |
| 107 | + |
| 108 | + // formatx! bar + label |
| 109 | + let _ = writeln!( |
| 110 | + svg, |
| 111 | + "<rect x=\"{x2}\" y=\"{y2}\" width=\"{bar_w}\" height=\"{h2}\" fill=\"#fb923c\"/>" |
| 112 | + ); |
| 113 | + let _ = writeln!( |
| 114 | + svg, |
| 115 | + "<text x=\"{}\" y=\"{}\" text-anchor=\"middle\" fill=\"#fed7aa\" font-size=\"10\">{:.0} ns</text>", |
| 116 | + x2 + bar_w / 2, |
| 117 | + y2 - 6, |
| 118 | + r.formatx_ns |
| 119 | + ); |
| 120 | + |
| 121 | + // X-axis label |
| 122 | + let center_x = gx + (2 * bar_w + gap) / 2; |
| 123 | + let _ = writeln!( |
| 124 | + svg, |
| 125 | + "<text x=\"{center_x}\" y=\"{}\" text-anchor=\"middle\" fill=\"#e2e8f0\" font-size=\"11\">{}</text>", |
| 126 | + bottom + 16, |
| 127 | + r.label |
| 128 | + ); |
| 129 | + } |
| 130 | + |
| 131 | + // Footer |
| 132 | + let _ = writeln!( |
| 133 | + svg, |
| 134 | + "<text x=\"{cx}\" y=\"{}\" text-anchor=\"middle\" fill=\"#64748b\" font-size=\"10\" font-style=\"italic\">Expected overhead - formatx! parses and resolves format strings at runtime.</text>", |
| 135 | + total_h - 10 |
| 136 | + ); |
| 137 | + |
| 138 | + let _ = writeln!(svg, "</svg>"); |
| 139 | + svg |
| 140 | +} |
| 141 | + |
| 142 | +struct BenchResult { |
| 143 | + label: &'static str, |
| 144 | + format_ns: f64, |
| 145 | + formatx_ns: f64, |
| 146 | +} |
| 147 | + |
| 148 | +fn bench<F: Fn() -> String>(f: F) -> f64 { |
| 149 | + for _ in 0..1000 { |
| 150 | + std::hint::black_box(f()); |
| 151 | + } |
| 152 | + let start = Instant::now(); |
| 153 | + for _ in 0..ITERATIONS { |
| 154 | + std::hint::black_box(f()); |
| 155 | + } |
| 156 | + start.elapsed().as_nanos() as f64 / ITERATIONS as f64 |
| 157 | +} |
| 158 | + |
| 159 | +fn main() { |
| 160 | + let results = vec![ |
| 161 | + BenchResult { |
| 162 | + label: "{} + {} = {}", |
| 163 | + format_ns: bench(|| format!("{} + {} = {}", 1, 2, 3)), |
| 164 | + formatx_ns: bench(|| formatx::formatx!("{} + {} = {}", 1, 2, 3).unwrap()), |
| 165 | + }, |
| 166 | + BenchResult { |
| 167 | + label: "{name} scored {score}%", |
| 168 | + format_ns: bench(|| format!("{name} scored {score}%", name = "Alice", score = 95)), |
| 169 | + formatx_ns: bench(|| { |
| 170 | + formatx::formatx!("{name} scored {score}%", name = "Alice", score = 95).unwrap() |
| 171 | + }), |
| 172 | + }, |
| 173 | + BenchResult { |
| 174 | + label: "{:+08.2}", |
| 175 | + format_ns: bench(|| format!("{:+08.2}", 3.14159)), |
| 176 | + formatx_ns: bench(|| formatx::formatx!("{:+08.2}", 3.14159).unwrap()), |
| 177 | + }, |
| 178 | + BenchResult { |
| 179 | + label: "{:-^20}", |
| 180 | + format_ns: bench(|| format!("{:-^20}", "center")), |
| 181 | + formatx_ns: bench(|| formatx::formatx!("{:-^20}", "center").unwrap()), |
| 182 | + }, |
| 183 | + ]; |
| 184 | + |
| 185 | + let svg = generate_svg(&results); |
| 186 | + fs::create_dir_all("images").ok(); |
| 187 | + fs::write("images/benchmark.svg", &svg).unwrap(); |
| 188 | +} |
0 commit comments