Skip to content

Commit 1c7c126

Browse files
authored
Merge pull request #39 from fastfloat/misc_improvements
Misc improvements
2 parents fa752df + 7498f77 commit 1c7c126

File tree

6 files changed

+180
-35
lines changed

6 files changed

+180
-35
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
build
22
build_debug
33
build_script
4+
outputs
5+
tags
46
compile_commands.json
57
.cache
8+
**/__pycache__

benchmarks/algorithms.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,10 @@ int ryu(T d, std::span<char>& buffer) {
217217

218218
template<arithmetic_float T>
219219
int teju_jagua(T d, std::span<char>& buffer) {
220+
if(d == 0.0) {
221+
std::copy_n("0E0", 3, buffer.data());
222+
return 3;
223+
}
220224
const auto fields = teju::traits_t<T>::teju(d);
221225
const bool sign = std::signbit(d);
222226
return to_chars(fields.mantissa, fields.exponent, sign, buffer.data());

benchmarks/benchmark.cpp

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,10 @@ struct diy_float_t {
4343
template <arithmetic_float T>
4444
void process(const std::vector<TestCase<T>> &lines,
4545
const std::vector<BenchArgs<T>> &args,
46-
const std::vector<std::string> &algo_filter) {
46+
const std::vector<std::string> &algo_filter,
47+
bool string_eval) {
4748
// We have a special algorithm for the string generation:
48-
if (!algo_filtered_out("just_string", algo_filter)) {
49+
if (string_eval && !algo_filtered_out("just_string", algo_filter)) {
4950
std::vector<diy_float_t> parsed;
5051
for(const auto d : lines) {
5152
const auto v = jkj::grisu_exact(d.value);
@@ -153,6 +154,8 @@ int main(int argc, char **argv) {
153154
cxxopts::value<std::string>()->default_value("uniform"))
154155
("s,single", "Use single precision instead of double.",
155156
cxxopts::value<bool>()->default_value("false"))
157+
("S,string-eval", "Evaluate perf. of string generation from decimal mantissa/exponent",
158+
cxxopts::value<bool>()->default_value("false"))
156159
("t,test", "Test the algorithms and find their properties.",
157160
cxxopts::value<bool>()->default_value("false"))
158161
("e,errol", "Enable errol3 (current impl. returns invalid values, e.g., for 0).",
@@ -204,14 +207,15 @@ int main(int argc, char **argv) {
204207
algorithms = initArgs<double>(errol, repeat, fixed_size);
205208

206209
const bool test = result["test"].as<bool>();
207-
std::visit([test, &filter](const auto &lines, const auto &args) {
210+
const bool string_eval = result["string-eval"].as<bool>();
211+
std::visit([test, string_eval, &filter](const auto &lines, const auto &args) {
208212
using T1 = typename std::decay_t<decltype(lines)>::value_type::Type;
209213
using T2 = typename std::decay_t<decltype(args)>::value_type::Type;
210214
if constexpr (std::is_same_v<T1, T2>) {
211215
if (test)
212216
evaluateProperties(lines, args, filter);
213217
else
214-
process(lines, args, filter);
218+
process(lines, args, filter, string_eval);
215219
}
216220
}, numbers, algorithms);
217221
} catch (const std::exception &e) {

benchmarks/random_generators.h

Lines changed: 54 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#define RANDOM_GENERATORS_H
33

44
#include <array>
5+
#include <bit>
56
#include <memory>
67
#include <random>
78
#include <iostream>
@@ -22,13 +23,42 @@ struct uniform_generator : float_number_generator<T> {
2223
explicit uniform_generator(T a = 0.0, T b = 1.0)
2324
: rd(), gen(rd()), dis(a, b) {}
2425
std::string describe() override {
25-
return std::string("generate random numbers uniformly in the interval [") +
26+
return "generate random numbers uniformly in the interval [" +
2627
std::to_string((dis.min)()) + std::string(",") +
2728
std::to_string((dis.max)()) + std::string("]");
2829
}
2930
T new_float() override { return dis(gen); }
3031
};
3132

33+
enum centering { centered, non_centered };
34+
template <std::floating_point T, centering C>
35+
struct centered_generator : float_number_generator<T> {
36+
constexpr static int MAN_BITS = sizeof(T) == 4 ? 23 : 52;
37+
constexpr static int EXP_BITS = sizeof(T) == 4 ? 8 : 11;
38+
std::random_device rd;
39+
std::mt19937_64 gen;
40+
std::uniform_int_distribution<uint32_t> dist_sign;
41+
std::uniform_int_distribution<uint32_t> dist_exp; // exclut 0=subnormal et max=inf/NaN
42+
std::uniform_int_distribution<uint64_t> dist_man; // mantisse, sauf le LSB
43+
explicit centered_generator()
44+
: rd(), gen(rd()), dist_sign(0, 1),
45+
dist_exp(1u << (EXP_BITS - 1), (1u << EXP_BITS) - 2),
46+
dist_man(1ull, (1ull << MAN_BITS) - 1) {}
47+
std::string describe() override {
48+
return "generate random "
49+
+ (C == centered ? std::string("centered") : std::string("non-centered"))
50+
+ " numbers uniformly among all normal floating-point numbers";
51+
}
52+
T new_float() override {
53+
using type_t = typename std::conditional_t< sizeof(T) == 4, uint32_t, uint64_t>;
54+
const type_t sign = dist_sign(gen);
55+
const type_t exp = dist_exp(gen);
56+
const type_t man = C == centered ? dist_man(gen) : 0u;
57+
const type_t bits = (sign << (EXP_BITS + MAN_BITS)) | (exp << MAN_BITS) | man;
58+
return std::bit_cast<T>(bits);
59+
}
60+
};
61+
3262
template <typename T>
3363
struct integer_uniform_generator : float_number_generator<T> {
3464
std::random_device rd;
@@ -37,8 +67,7 @@ struct integer_uniform_generator : float_number_generator<T> {
3767
explicit integer_uniform_generator(long a = LONG_MIN, long b = LONG_MAX)
3868
: rd(), gen(rd()), dis(a, b) {}
3969
std::string describe() override {
40-
return std::string(
41-
"generate random integers numbers uniformly in the interval [") +
70+
return "generate random integers numbers uniformly in the interval [" +
4271
std::to_string((dis.min)()) + std::string(",") +
4372
std::to_string((dis.max)()) + std::string("]");
4473
}
@@ -47,9 +76,8 @@ struct integer_uniform_generator : float_number_generator<T> {
4776

4877
template <typename T>
4978
struct simple_uniform : float_number_generator<T> {
50-
using gen_type = std::conditional_t<sizeof(T) == 4, std::mt19937, std::mt19937_64>;
5179
std::random_device rd;
52-
gen_type gen;
80+
std::mt19937_64 gen;
5381
explicit simple_uniform() : rd(), gen(rd()) {}
5482
std::string describe() override { return "rand() / 0xFFFFFFFF "; }
5583
T new_float() override {
@@ -60,7 +88,6 @@ struct simple_uniform : float_number_generator<T> {
6088

6189
template <typename T>
6290
struct simple_int : float_number_generator<T> {
63-
using gen_type = std::conditional_t<sizeof(T) == 4, std::mt19937, std::mt19937_64>;
6491
std::random_device rd;
6592
std::mt19937_64 gen;
6693
std::string describe() override { return "rand()"; }
@@ -70,9 +97,8 @@ struct simple_int : float_number_generator<T> {
7097

7198
template <typename T>
7299
struct one_over_rand : float_number_generator<T> {
73-
using gen_type = std::conditional_t<sizeof(T) == 4, std::mt19937, std::mt19937_64>;
74100
std::random_device rd;
75-
gen_type gen;
101+
std::mt19937_64 gen;
76102
explicit one_over_rand() : rd(), gen(rd()) {}
77103
std::string describe() override { return "1 / rand()"; }
78104
T new_float() override {
@@ -85,9 +111,10 @@ struct one_over_rand : float_number_generator<T> {
85111
}
86112
};
87113

88-
constexpr std::array<const char*, 5> model_names = {
89-
"uniform", "integer_uniform",
90-
"simple_uniform", "simple_int",
114+
constexpr std::array<const char*, 8> model_names = {
115+
"uniform_01" , "uniform_all" , "integer_uniform" ,
116+
"centered" , "non_centered" ,
117+
"simple_uniform" , "simple_int" ,
91118
"one_over_rand"
92119
};
93120

@@ -101,23 +128,29 @@ get_generator_by_name(std::string name) {
101128
std::cout << std::endl;
102129

103130
// This is naive, but also not very important.
104-
if (name == "uniform") {
131+
if (name == "uniform_01")
105132
return std::unique_ptr<float_number_generator<T>>(new uniform_generator<T>());
133+
if (name == "uniform_all") {
134+
return std::unique_ptr<float_number_generator<T>>(
135+
new uniform_generator<T>(std::numeric_limits<T>::lowest(),
136+
std::numeric_limits<T>::max())
137+
);
106138
}
107-
if (name == "integer_uniform") {
139+
if (name == "centered")
140+
return std::unique_ptr<float_number_generator<T>>(new centered_generator<T, centered>());
141+
if (name == "non_centered")
142+
return std::unique_ptr<float_number_generator<T>>(new centered_generator<T, non_centered>());
143+
if (name == "integer_uniform")
108144
return std::unique_ptr<float_number_generator<T>>(new integer_uniform_generator<T>());
109-
}
110-
if (name == "simple_uniform") {
145+
if (name == "simple_uniform")
111146
return std::unique_ptr<float_number_generator<T>>(new simple_uniform<T>());
112-
}
113-
if (name == "simple_int") {
147+
if (name == "simple_int")
114148
return std::unique_ptr<float_number_generator<T>>(new simple_int<T>());
115-
}
116-
if (name == "one_over_rand") {
149+
if (name == "one_over_rand")
117150
return std::unique_ptr<float_number_generator<T>>(new one_over_rand<T>());
118-
}
151+
119152
std::cerr << " I do not recognize " << name << std::endl;
120-
std::cerr << " Warning: falling back on uniform generator. " << std::endl;
153+
std::cerr << " Warning: falling back on uniform_01 generator. " << std::endl;
121154
return std::unique_ptr<float_number_generator<T>>(new uniform_generator<T>());
122155
}
123156

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
#!/usr/bin/env python3
2+
import subprocess
3+
import os
4+
import platform
5+
from latex_table import generate_latex_table
6+
7+
# Configuration
8+
benchmark_executable = './build/benchmarks/benchmark'
9+
latex_script = './scripts/latex_table.py'
10+
output_dir = './outputs'
11+
input_files = [
12+
'data/canada.txt',
13+
'data/mesh.txt',
14+
]
15+
models = [
16+
'uniform_01',
17+
'uniform_all',
18+
'integer_uniform',
19+
'centered',
20+
'non_centered',
21+
]
22+
runs_r = 1_000
23+
volume_v = 1_000_000
24+
flag_combinations = [
25+
[],
26+
['-F6'],
27+
['-s'],
28+
['-F6', '-s'],
29+
]
30+
31+
32+
def get_cpu_model():
33+
if platform.system() == "Windows":
34+
return platform.processor()
35+
elif platform.system() == "Darwin":
36+
os.environ['PATH'] = os.environ['PATH'] + os.pathsep + '/usr/sbin'
37+
command = "sysctl -n machdep.cpu.brand_string"
38+
return subprocess.check_output(command).strip()
39+
elif platform.system() == "Linux":
40+
command = "cat /proc/cpuinfo"
41+
output = subprocess.check_output(command, shell=True).decode().strip()
42+
for line in output.split("\n"):
43+
if line.startswith("model name"):
44+
return line.split(':', 1)[1].strip()
45+
return "unknown_cpu"
46+
47+
48+
CPUModel = get_cpu_model().replace(' ', '_').replace('/', '-').replace('@', '')
49+
os.makedirs(output_dir, exist_ok=True)
50+
51+
52+
# Helper to run a command and return its stdout
53+
def run_cmd(cmd):
54+
result = subprocess.run(cmd, capture_output=True, text=True)
55+
result.check_returncode()
56+
return result.stdout
57+
58+
59+
# Process a single benchmark invocation and generate .tex
60+
def process_job(label, cmd_args, flags):
61+
# Run the benchmark
62+
cmd = [benchmark_executable] + cmd_args + flags
63+
print(f"Running: {' '.join(cmd)}")
64+
output = run_cmd(cmd)
65+
66+
# Build output file name
67+
flag_label = ''.join([f.strip('-') for f in flags]) or 'none'
68+
safe_label = label.replace('.', '_')
69+
filename = f"{CPUModel}_{safe_label}_{flag_label}.tex"
70+
out_path = os.path.join(output_dir, filename)
71+
72+
# Write to file
73+
tex_content = generate_latex_table(output)
74+
with open(out_path, 'w') as f:
75+
f.write(tex_content)
76+
print(f"Written: {out_path}\n")
77+
78+
79+
if __name__ == '__main__':
80+
# File-based benchmarks
81+
for filepath in input_files:
82+
file_label = os.path.splitext(os.path.basename(filepath))[0]
83+
for flags in flag_combinations:
84+
process_job(
85+
label=file_label,
86+
cmd_args=['-f', filepath, '-r', str(runs_r)],
87+
flags=flags
88+
)
89+
90+
# Model-based benchmarks
91+
for model in models:
92+
for flags in flag_combinations:
93+
process_job(
94+
label=model,
95+
cmd_args=['-m', model, '-v', str(volume_v), '-r', str(runs_r)],
96+
flags=flags
97+
)

scripts/latex_table.py

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
#!/usr/bin/env python3
2-
32
import sys
43
import re
54
import argparse
65

6+
77
# Function to format a number to two significant digits
88
def format_to_two_sig_digits(value):
99
if not isinstance(value, (int, float)) or value == 0:
1010
return "N/A"
11-
11+
1212
# Handle negative numbers
1313
is_negative = value < 0
1414
abs_value = abs(value)
@@ -27,14 +27,15 @@ def format_to_two_sig_digits(value):
2727

2828
# Format the number
2929
if exponent >= 0 and exponent <= 4:
30-
format = f"{'-' if is_negative else ''}{abs_value*10**exponent}"
30+
format = f"{'-' if is_negative else ''}{abs_value * 10 ** exponent}"
3131
format = format.replace(".0", "")
3232
return format
3333
elif exponent < 0 and exponent >= -4:
34-
return f"{'-' if is_negative else ''}{abs_value*10**exponent:.1f}"
34+
return f"{'-' if is_negative else ''}{abs_value * 10 ** exponent:.1f}"
3535
else:
3636
return f"{'-' if is_negative else ''}{abs_value:.1f}e{exponent}"
3737

38+
3839
# Function to parse the raw input data
3940
def parse_input(data):
4041
lines = data.splitlines()
@@ -54,6 +55,7 @@ def parse_input(data):
5455
parsed_data.append(current_entry)
5556
if not current_entry:
5657
continue
58+
5759
# Match lines with ns/f
5860
match_ns = re.search(r"([\d.]+)\s*ns/f", line)
5961
if match_ns and current_entry:
@@ -72,27 +74,29 @@ def parse_input(data):
7274
# Filter out incomplete entries
7375
return parsed_data
7476

77+
7578
# Function to generate LaTeX table
76-
def generate_latex_table(data):
79+
def generate_latex_table(raw_input):
80+
data = parse_input(raw_input)
81+
7782
latex_table = r"""
7883
\begin{tabular}{lccc}
7984
\toprule
8085
\textbf{Name} & \textbf{ns/f} & \textbf{instructions/float} & \textbf{instructions/cycle} \\
8186
\midrule
8287
"""
83-
8488
for entry in data:
8589
name = entry["name"].replace("_", "\\_") # Escape underscores for LaTeX
8690
ns_per_float = format_to_two_sig_digits(entry['ns_per_float']) if 'ns_per_float' in entry else 'N/A'
8791
inst_per_float = format_to_two_sig_digits(entry['inst_per_float']) if 'inst_per_float' in entry else 'N/A'
8892
inst_per_cycle = format_to_two_sig_digits(entry['inst_per_cycle']) if 'inst_per_cycle' in entry else 'N/A'
8993
latex_table += f"{name} & {ns_per_float} & {inst_per_float} & {inst_per_cycle} \\\\ \n"
90-
9194
latex_table += r"""\bottomrule
9295
\end{tabular}
9396
"""
9497
return latex_table
9598

99+
96100
if __name__ == "__main__":
97101
parser = argparse.ArgumentParser(description="Generate LaTeX table from performance data")
98102
parser.add_argument("file", nargs="?", help="Optional input file name (if not provided, reads from stdin)")
@@ -111,6 +115,6 @@ def generate_latex_table(data):
111115
sys.exit(1)
112116
else:
113117
raw_input = sys.stdin.read()
114-
parsed_data = parse_input(raw_input)
115-
latex_output = generate_latex_table(parsed_data)
116-
print(latex_output)
118+
119+
latex_output = generate_latex_table(raw_input)
120+
print(latex_output)

0 commit comments

Comments
 (0)