Skip to content

Commit 2afc341

Browse files
authored
Merge pull request #35 from fastfloat/latex_table
script to generate LaTeX tables from our benchmark outputs
2 parents ff76a9d + 96fefa1 commit 2afc341

File tree

2 files changed

+135
-0
lines changed

2 files changed

+135
-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: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
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+
# Skip empty lines or comments
47+
if not line or line.startswith("#"):
48+
continue
49+
50+
# Match lines that start a new entry (e.g., "just_string : 1365.92 MB/s ...")
51+
match_entry = re.match(r"(\S+)\s*:\s*[\d.]+\s*MB/s", line)
52+
if match_entry:
53+
current_entry = {"name": match_entry.group(1)}
54+
parsed_data.append(current_entry)
55+
if not current_entry:
56+
continue
57+
# Match lines with ns/f
58+
match_ns = re.search(r"([\d.]+)\s*ns/f", line)
59+
if match_ns and current_entry:
60+
current_entry["ns_per_float"] = float(match_ns.group(1))
61+
62+
# Match lines with instructions/float (i/f)
63+
match_inst_float = re.search(r"([\d.]+)\s*i/f", line)
64+
if match_inst_float and current_entry:
65+
current_entry["inst_per_float"] = float(match_inst_float.group(1))
66+
67+
# Match lines with instructions/cycle (i/c)
68+
match_inst_cycle = re.search(r"([\d.]+)\s*i/c", line)
69+
if match_inst_cycle and current_entry:
70+
current_entry["inst_per_cycle"] = float(match_inst_cycle.group(1))
71+
72+
# Filter out incomplete entries
73+
return parsed_data
74+
75+
# Function to generate LaTeX table
76+
def generate_latex_table(data):
77+
latex_table = r"""
78+
\begin{tabular}{lccc}
79+
\toprule
80+
\textbf{Name} & \textbf{ns/f} & \textbf{instructions/float} & \textbf{instructions/cycle} \\
81+
\midrule
82+
"""
83+
84+
for entry in data:
85+
name = entry["name"].replace("_", "\\_") # Escape underscores for LaTeX
86+
ns_per_float = format_to_two_sig_digits(entry['ns_per_float']) if 'ns_per_float' in entry else 'N/A'
87+
inst_per_float = format_to_two_sig_digits(entry['inst_per_float']) if 'inst_per_float' in entry else 'N/A'
88+
inst_per_cycle = format_to_two_sig_digits(entry['inst_per_cycle']) if 'inst_per_cycle' in entry else 'N/A'
89+
latex_table += f"{name} & {ns_per_float} & {inst_per_float} & {inst_per_cycle} \\\\ \n"
90+
91+
latex_table += r"""\bottomrule
92+
\end{tabular}
93+
"""
94+
return latex_table
95+
96+
if __name__ == "__main__":
97+
parser = argparse.ArgumentParser(description="Generate LaTeX table from performance data")
98+
parser.add_argument("file", nargs="?", help="Optional input file name (if not provided, reads from stdin)")
99+
args = parser.parse_args()
100+
101+
# Read input data
102+
if args.file:
103+
try:
104+
with open(args.file, "r") as f:
105+
raw_input = f.read()
106+
except FileNotFoundError:
107+
print(f"Error: File '{args.file}' not found.", file=sys.stderr)
108+
sys.exit(1)
109+
except IOError as e:
110+
print(f"Error reading file '{args.file}': {e}", file=sys.stderr)
111+
sys.exit(1)
112+
else:
113+
raw_input = sys.stdin.read()
114+
parsed_data = parse_input(raw_input)
115+
latex_output = generate_latex_table(parsed_data)
116+
print(latex_output)

0 commit comments

Comments
 (0)