Skip to content

Commit 4badb06

Browse files
committed
script to generate LaTeX tables from our benchmark outputs
1 parent 59f508c commit 4badb06

File tree

2 files changed

+147
-0
lines changed

2 files changed

+147
-0
lines changed

scripts/README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
## Creating LaTeX tables
2+
3+
Prerequisite: You should be able to build and run the C++ benchmark. You need Python 3 on your system.
4+
5+
Run your benchmark:
6+
7+
```
8+
cmake -B build
9+
./build/benchmarks/benchmark -f data/canada.txt > myresults.txt
10+
```
11+
12+
Process the raw output:
13+
14+
```
15+
./scripts/latex_table.py myresults.txt
16+
```
17+
18+
This will print out to std out the table. The numbers are already rounded to two significant digits,
19+
ready to be included in a scientific manuscript.

scripts/latex_table.py

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
#!/usr/bin/env python3
2+
3+
import sys
4+
import re
5+
import argparse
6+
7+
# Function to format a number to two significant digits
8+
def format_to_two_sig_digits(value):
9+
if not isinstance(value, (int, float)) or value == 0:
10+
return "N/A"
11+
12+
# Handle negative numbers
13+
is_negative = value < 0
14+
abs_value = abs(value)
15+
16+
exponent = 0
17+
18+
while abs_value >= 100:
19+
abs_value /= 10
20+
exponent += 1
21+
while abs_value < 10:
22+
abs_value *= 10
23+
exponent -= 1
24+
25+
# Round to two significant digits
26+
abs_value = round(abs_value, 0)
27+
28+
# Format the number
29+
if exponent >= 0 and exponent <= 4:
30+
format = f"{'-' if is_negative else ''}{abs_value*10**exponent}"
31+
format = format.replace(".0", "")
32+
return format
33+
elif exponent < 0 and exponent >= -4:
34+
return f"{'-' if is_negative else ''}{abs_value*10**exponent:.1f}"
35+
else:
36+
return f"{'-' if is_negative else ''}{abs_value:.1f}e{exponent}"
37+
38+
# Function to parse the raw input data
39+
def parse_input(data):
40+
lines = data.splitlines()
41+
parsed_data = []
42+
current_entry = None
43+
44+
for line in lines:
45+
line = line.strip()
46+
print(f"Processing line: {line}") # Debugging output
47+
# Skip empty lines or comments
48+
if not line or line.startswith("#"):
49+
continue
50+
51+
# Match lines that start a new entry (e.g., "just_string : 1365.92 MB/s ...")
52+
match_entry = re.match(r"(\S+)\s*:\s*[\d.]+\s*MB/s", line)
53+
if match_entry:
54+
print(f"Found new entry: {match_entry.group(1)}") # Debugging output
55+
current_entry = {"name": match_entry.group(1)}
56+
parsed_data.append(current_entry)
57+
if not current_entry:
58+
continue
59+
print(f"reviewing line {line}") # Debugging output
60+
61+
# Match lines with ns/f
62+
match_ns = re.search(r"([\d.]+)\s*ns/f", line)
63+
if match_ns and current_entry:
64+
print(f"Found ns/f: {match_ns.group(1)}")
65+
current_entry["ns_per_float"] = float(match_ns.group(1))
66+
67+
# Match lines with instructions/float (i/f)
68+
match_inst_float = re.search(r"([\d.]+)\s*i/f", line)
69+
if match_inst_float and current_entry:
70+
print(f"Found i/f: {match_inst_float.group(1)}")
71+
current_entry["inst_per_float"] = float(match_inst_float.group(1))
72+
73+
# Match lines with instructions/cycle (i/c)
74+
match_inst_cycle = re.search(r"([\d.]+)\s*i/c", line)
75+
if match_inst_cycle and current_entry:
76+
print(f"Found i/c: {match_inst_cycle.group(1)}")
77+
current_entry["inst_per_cycle"] = float(match_inst_cycle.group(1))
78+
79+
# Filter out incomplete entries
80+
return parsed_data
81+
82+
# Function to generate LaTeX table
83+
def generate_latex_table(data):
84+
latex_table = r"""
85+
\begin{tabular}{lccc}
86+
\toprule
87+
\textbf{Name} & \textbf{ns/f} & \textbf{instructions/float} & \textbf{instructions/cycle} \\
88+
\midrule
89+
"""
90+
91+
for entry in data:
92+
name = entry["name"].replace("_", "\\_") # Escape underscores for LaTeX
93+
ns_per_float = format_to_two_sig_digits(entry['ns_per_float']) if 'ns_per_float' in entry else 'N/A'
94+
inst_per_float = format_to_two_sig_digits(entry['inst_per_float']) if 'inst_per_float' in entry else 'N/A'
95+
inst_per_cycle = format_to_two_sig_digits(entry['inst_per_cycle']) if 'inst_per_cycle' in entry else 'N/A'
96+
latex_table += f"{name} & {ns_per_float} & {inst_per_float} & {inst_per_cycle} \\\\ \n"
97+
98+
latex_table += r"""\bottomrule
99+
\end{tabular}
100+
"""
101+
return latex_table
102+
103+
if __name__ == "f__main__":
104+
print(format_to_two_sig_digits(336.0))
105+
106+
107+
if __name__ == "__main__":
108+
parser = argparse.ArgumentParser(description="Generate LaTeX table from performance data")
109+
parser.add_argument("file", nargs="?", help="Optional input file name (if not provided, reads from stdin)")
110+
args = parser.parse_args()
111+
112+
# Read input data
113+
if args.file:
114+
try:
115+
with open(args.file, "r") as f:
116+
raw_input = f.read()
117+
except FileNotFoundError:
118+
print(f"Error: File '{args.file}' not found.", file=sys.stderr)
119+
sys.exit(1)
120+
except IOError as e:
121+
print(f"Error reading file '{args.file}': {e}", file=sys.stderr)
122+
sys.exit(1)
123+
else:
124+
raw_input = sys.stdin.read()
125+
parsed_data = parse_input(raw_input)
126+
print(f"Parsed data: {parsed_data}") # Debugging output
127+
latex_output = generate_latex_table(parsed_data)
128+
print(latex_output)

0 commit comments

Comments
 (0)