Skip to content

Commit 8a7bedc

Browse files
committed
Add a script for nice markdown text
1 parent ba42cc6 commit 8a7bedc

1 file changed

Lines changed: 171 additions & 0 deletions

File tree

benches/bench_compare.py

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
#!/usr/bin/env python3
2+
"""Parse Criterion benchmark output files and produce comparison tables.
3+
4+
Usage:
5+
python3 bench_compare.py "Column Title" path/to/bench.txt ["Title2" path2.txt ...]
6+
python3 bench_compare.py --speedup "Faster" file1.txt "Slower" file2.txt
7+
nix shell nixpkgs#python3 -c python3 bench_compare.py ...
8+
9+
The language prefix (e.g. "java-") is extracted from bench names and appended
10+
to the column title automatically.
11+
12+
With --speedup, exactly two columns are expected and the output is a speedup
13+
table showing how much faster the first column is vs the second (ratio > 1
14+
means first is faster, < 1 means second is faster).
15+
"""
16+
17+
import re
18+
import sys
19+
from collections import OrderedDict
20+
21+
UNIT_TO_NS = {'ns': 1, 'µs': 1e3, 'us': 1e3, 'ms': 1e6, 's': 1e9}
22+
23+
24+
def parse_bench_file(path):
25+
"""Parse a Criterion benchmark output file.
26+
27+
Returns (language, results) where results maps
28+
(category, bench_name) -> nanoseconds as float.
29+
"""
30+
results = {}
31+
current_bench = None
32+
language = None
33+
34+
name_re = re.compile(r'^(\S+?)/(\w+?)-(\S+?)(?:\s+time:|\s*$)')
35+
time_re = re.compile(r'time:\s+\[[\d.]+ \S+ ([\d.]+) (ns|µs|us|ms|s) [\d.]+ \S+\]')
36+
37+
with open(path) as f:
38+
for line in f:
39+
stripped = line.strip()
40+
41+
name_m = name_re.match(stripped)
42+
if name_m:
43+
language = language or name_m.group(2)
44+
current_bench = (name_m.group(1), name_m.group(3))
45+
46+
time_m = time_re.search(line)
47+
if time_m and current_bench:
48+
ns = float(time_m.group(1)) * UNIT_TO_NS[time_m.group(2)]
49+
results[current_bench] = ns
50+
current_bench = None
51+
52+
return language, results
53+
54+
55+
def format_time(ns):
56+
if ns < 1000:
57+
return f"{ns:.2f} ns" if ns >= 10 else f"{ns:.1f} ns"
58+
elif ns < 1e6:
59+
return f"{ns / 1e3:.2f} us"
60+
elif ns < 1e9:
61+
return f"{ns / 1e6:.2f} ms"
62+
else:
63+
return f"{ns / 1e9:.2f} s"
64+
65+
66+
def format_speedup(ratio):
67+
if ratio >= 10:
68+
return f"{ratio:.0f}x"
69+
else:
70+
return f"{ratio:.1f}x"
71+
72+
73+
def collect_categories(all_results):
74+
categories = OrderedDict()
75+
for results in all_results:
76+
for cat, bench in results:
77+
if cat not in categories:
78+
categories[cat] = OrderedDict()
79+
if bench not in categories[cat]:
80+
categories[cat][bench] = True
81+
return categories
82+
83+
84+
def print_comparison(columns, all_results):
85+
categories = collect_categories(all_results)
86+
87+
for cat, benches in categories.items():
88+
print(f"### {cat}\n")
89+
header = "Test Case | " + " | ".join(columns)
90+
sep = "-- | " + " | ".join("--" for _ in columns)
91+
print(header)
92+
print(sep)
93+
94+
for bench in benches:
95+
values = []
96+
for results in all_results:
97+
ns = results.get((cat, bench))
98+
values.append(format_time(ns) if ns is not None else "")
99+
print(f"{bench} | " + " | ".join(values))
100+
101+
print()
102+
103+
104+
def print_speedup(columns, all_results):
105+
categories = collect_categories(all_results)
106+
a_results, b_results = all_results
107+
title = f"{columns[0]} vs {columns[1]} Speedup"
108+
109+
cat_names = list(categories.keys())
110+
111+
print(f"### {title}\n")
112+
header = "Test Case | " + " | ".join(f"{cat} Speedup" for cat in cat_names)
113+
sep = "-- | " + " | ".join("--" for _ in cat_names)
114+
print(header)
115+
print(sep)
116+
117+
all_benches = OrderedDict()
118+
for benches in categories.values():
119+
for b in benches:
120+
all_benches[b] = True
121+
122+
for bench in all_benches:
123+
values = []
124+
for cat in cat_names:
125+
a_ns = a_results.get((cat, bench))
126+
b_ns = b_results.get((cat, bench))
127+
if a_ns is not None and b_ns is not None and a_ns > 0:
128+
values.append(format_speedup(b_ns / a_ns))
129+
else:
130+
values.append("")
131+
print(f"{bench} | " + " | ".join(values))
132+
133+
print()
134+
135+
136+
def main():
137+
args = sys.argv[1:]
138+
139+
speedup = False
140+
if "--speedup" in args:
141+
speedup = True
142+
args.remove("--speedup")
143+
144+
if len(args) < 2 or len(args) % 2 != 0:
145+
print("Usage: bench_compare.py [--speedup] \"Title1\" file1.txt [\"Title2\" file2.txt ...]",
146+
file=sys.stderr)
147+
sys.exit(1)
148+
149+
if speedup and len(args) != 4:
150+
print("--speedup requires exactly two title/file pairs", file=sys.stderr)
151+
sys.exit(1)
152+
153+
columns = []
154+
all_results = []
155+
for i in range(0, len(args), 2):
156+
title = args[i]
157+
path = args[i + 1]
158+
lang, results = parse_bench_file(path)
159+
if lang:
160+
title = f"{title} ({lang.capitalize()})"
161+
columns.append(title)
162+
all_results.append(results)
163+
164+
if speedup:
165+
print_speedup(columns, all_results)
166+
else:
167+
print_comparison(columns, all_results)
168+
169+
170+
if __name__ == "__main__":
171+
main()

0 commit comments

Comments
 (0)