Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
build
build_debug
build_script
outputs
tags
compile_commands.json
.cache
**/__pycache__
4 changes: 4 additions & 0 deletions benchmarks/algorithms.h
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,10 @@ int ryu(T d, std::span<char>& buffer) {

template<arithmetic_float T>
int teju_jagua(T d, std::span<char>& buffer) {
if(d == 0.0) {
std::copy_n("0E0", 3, buffer.data());
return 3;
}
const auto fields = teju::traits_t<T>::teju(d);
const bool sign = std::signbit(d);
return to_chars(fields.mantissa, fields.exponent, sign, buffer.data());
Expand Down
12 changes: 8 additions & 4 deletions benchmarks/benchmark.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,10 @@ struct diy_float_t {
template <arithmetic_float T>
void process(const std::vector<TestCase<T>> &lines,
const std::vector<BenchArgs<T>> &args,
const std::vector<std::string> &algo_filter) {
const std::vector<std::string> &algo_filter,
bool string_eval) {
// We have a special algorithm for the string generation:
if (!algo_filtered_out("just_string", algo_filter)) {
if (string_eval && !algo_filtered_out("just_string", algo_filter)) {
std::vector<diy_float_t> parsed;
for(const auto d : lines) {
const auto v = jkj::grisu_exact(d.value);
Expand Down Expand Up @@ -153,6 +154,8 @@ int main(int argc, char **argv) {
cxxopts::value<std::string>()->default_value("uniform"))
("s,single", "Use single precision instead of double.",
cxxopts::value<bool>()->default_value("false"))
("S,string-eval", "Evaluate perf. of string generation from decimal mantissa/exponent",
cxxopts::value<bool>()->default_value("false"))
("t,test", "Test the algorithms and find their properties.",
cxxopts::value<bool>()->default_value("false"))
("e,errol", "Enable errol3 (current impl. returns invalid values, e.g., for 0).",
Expand Down Expand Up @@ -204,14 +207,15 @@ int main(int argc, char **argv) {
algorithms = initArgs<double>(errol, repeat, fixed_size);

const bool test = result["test"].as<bool>();
std::visit([test, &filter](const auto &lines, const auto &args) {
const bool string_eval = result["string-eval"].as<bool>();
std::visit([test, string_eval, &filter](const auto &lines, const auto &args) {
using T1 = typename std::decay_t<decltype(lines)>::value_type::Type;
using T2 = typename std::decay_t<decltype(args)>::value_type::Type;
if constexpr (std::is_same_v<T1, T2>) {
if (test)
evaluateProperties(lines, args, filter);
else
process(lines, args, filter);
process(lines, args, filter, string_eval);
}
}, numbers, algorithms);
} catch (const std::exception &e) {
Expand Down
75 changes: 54 additions & 21 deletions benchmarks/random_generators.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#define RANDOM_GENERATORS_H

#include <array>
#include <bit>
#include <memory>
#include <random>
#include <iostream>
Expand All @@ -22,13 +23,42 @@ struct uniform_generator : float_number_generator<T> {
explicit uniform_generator(T a = 0.0, T b = 1.0)
: rd(), gen(rd()), dis(a, b) {}
std::string describe() override {
return std::string("generate random numbers uniformly in the interval [") +
return "generate random numbers uniformly in the interval [" +
std::to_string((dis.min)()) + std::string(",") +
std::to_string((dis.max)()) + std::string("]");
}
T new_float() override { return dis(gen); }
};

enum centering { centered, non_centered };
template <std::floating_point T, centering C>
struct centered_generator : float_number_generator<T> {
constexpr static int MAN_BITS = sizeof(T) == 4 ? 23 : 52;
constexpr static int EXP_BITS = sizeof(T) == 4 ? 8 : 11;
std::random_device rd;
std::mt19937_64 gen;
std::uniform_int_distribution<uint32_t> dist_sign;
std::uniform_int_distribution<uint32_t> dist_exp; // exclut 0=subnormal et max=inf/NaN
std::uniform_int_distribution<uint64_t> dist_man; // mantisse, sauf le LSB
explicit centered_generator()
: rd(), gen(rd()), dist_sign(0, 1),
dist_exp(1u << (EXP_BITS - 1), (1u << EXP_BITS) - 2),
dist_man(1ull, (1ull << MAN_BITS) - 1) {}
std::string describe() override {
return "generate random "
+ (C == centered ? std::string("centered") : std::string("non-centered"))
+ " numbers uniformly among all normal floating-point numbers";
}
T new_float() override {
using type_t = typename std::conditional_t< sizeof(T) == 4, uint32_t, uint64_t>;
const type_t sign = dist_sign(gen);
const type_t exp = dist_exp(gen);
const type_t man = C == centered ? dist_man(gen) : 0u;
const type_t bits = (sign << (EXP_BITS + MAN_BITS)) | (exp << MAN_BITS) | man;
return std::bit_cast<T>(bits);
}
};

template <typename T>
struct integer_uniform_generator : float_number_generator<T> {
std::random_device rd;
Expand All @@ -37,8 +67,7 @@ struct integer_uniform_generator : float_number_generator<T> {
explicit integer_uniform_generator(long a = LONG_MIN, long b = LONG_MAX)
: rd(), gen(rd()), dis(a, b) {}
std::string describe() override {
return std::string(
"generate random integers numbers uniformly in the interval [") +
return "generate random integers numbers uniformly in the interval [" +
std::to_string((dis.min)()) + std::string(",") +
std::to_string((dis.max)()) + std::string("]");
}
Expand All @@ -47,9 +76,8 @@ struct integer_uniform_generator : float_number_generator<T> {

template <typename T>
struct simple_uniform : float_number_generator<T> {
using gen_type = std::conditional_t<sizeof(T) == 4, std::mt19937, std::mt19937_64>;
std::random_device rd;
gen_type gen;
std::mt19937_64 gen;
explicit simple_uniform() : rd(), gen(rd()) {}
std::string describe() override { return "rand() / 0xFFFFFFFF "; }
T new_float() override {
Expand All @@ -60,7 +88,6 @@ struct simple_uniform : float_number_generator<T> {

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

template <typename T>
struct one_over_rand : float_number_generator<T> {
using gen_type = std::conditional_t<sizeof(T) == 4, std::mt19937, std::mt19937_64>;
std::random_device rd;
gen_type gen;
std::mt19937_64 gen;
explicit one_over_rand() : rd(), gen(rd()) {}
std::string describe() override { return "1 / rand()"; }
T new_float() override {
Expand All @@ -85,9 +111,10 @@ struct one_over_rand : float_number_generator<T> {
}
};

constexpr std::array<const char*, 5> model_names = {
"uniform", "integer_uniform",
"simple_uniform", "simple_int",
constexpr std::array<const char*, 8> model_names = {
"uniform_01" , "uniform_all" , "integer_uniform" ,
"centered" , "non_centered" ,
"simple_uniform" , "simple_int" ,
"one_over_rand"
};

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

// This is naive, but also not very important.
if (name == "uniform") {
if (name == "uniform_01")
return std::unique_ptr<float_number_generator<T>>(new uniform_generator<T>());
if (name == "uniform_all") {
return std::unique_ptr<float_number_generator<T>>(
new uniform_generator<T>(std::numeric_limits<T>::lowest(),
std::numeric_limits<T>::max())
);
}
if (name == "integer_uniform") {
if (name == "centered")
return std::unique_ptr<float_number_generator<T>>(new centered_generator<T, centered>());
if (name == "non_centered")
return std::unique_ptr<float_number_generator<T>>(new centered_generator<T, non_centered>());
if (name == "integer_uniform")
return std::unique_ptr<float_number_generator<T>>(new integer_uniform_generator<T>());
}
if (name == "simple_uniform") {
if (name == "simple_uniform")
return std::unique_ptr<float_number_generator<T>>(new simple_uniform<T>());
}
if (name == "simple_int") {
if (name == "simple_int")
return std::unique_ptr<float_number_generator<T>>(new simple_int<T>());
}
if (name == "one_over_rand") {
if (name == "one_over_rand")
return std::unique_ptr<float_number_generator<T>>(new one_over_rand<T>());
}

std::cerr << " I do not recognize " << name << std::endl;
std::cerr << " Warning: falling back on uniform generator. " << std::endl;
std::cerr << " Warning: falling back on uniform_01 generator. " << std::endl;
return std::unique_ptr<float_number_generator<T>>(new uniform_generator<T>());
}

Expand Down
97 changes: 97 additions & 0 deletions scripts/generate_multiple_tables.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
#!/usr/bin/env python3
import subprocess
import os
import platform
from latex_table import generate_latex_table

# Configuration
benchmark_executable = './build/benchmarks/benchmark'
latex_script = './scripts/latex_table.py'
output_dir = './outputs'
input_files = [
'data/canada.txt',
'data/mesh.txt',
]
models = [
'uniform_01',
'uniform_all',
'integer_uniform',
'centered',
'non_centered',
]
runs_r = 1_000
volume_v = 1_000_000
flag_combinations = [
[],
['-F6'],
['-s'],
['-F6', '-s'],
]


def get_cpu_model():
if platform.system() == "Windows":
return platform.processor()
elif platform.system() == "Darwin":
os.environ['PATH'] = os.environ['PATH'] + os.pathsep + '/usr/sbin'
command = "sysctl -n machdep.cpu.brand_string"
return subprocess.check_output(command).strip()
elif platform.system() == "Linux":
command = "cat /proc/cpuinfo"
output = subprocess.check_output(command, shell=True).decode().strip()
for line in output.split("\n"):
if line.startswith("model name"):
return line.split(':', 1)[1].strip()
return "unknown_cpu"


CPUModel = get_cpu_model().replace(' ', '_').replace('/', '-').replace('@', '')
os.makedirs(output_dir, exist_ok=True)


# Helper to run a command and return its stdout
def run_cmd(cmd):
result = subprocess.run(cmd, capture_output=True, text=True)
result.check_returncode()
return result.stdout


# Process a single benchmark invocation and generate .tex
def process_job(label, cmd_args, flags):
# Run the benchmark
cmd = [benchmark_executable] + cmd_args + flags
print(f"Running: {' '.join(cmd)}")
output = run_cmd(cmd)

# Build output file name
flag_label = ''.join([f.strip('-') for f in flags]) or 'none'
safe_label = label.replace('.', '_')
filename = f"{CPUModel}_{safe_label}_{flag_label}.tex"
out_path = os.path.join(output_dir, filename)

# Write to file
tex_content = generate_latex_table(output)
with open(out_path, 'w') as f:
f.write(tex_content)
print(f"Written: {out_path}\n")


if __name__ == '__main__':
# File-based benchmarks
for filepath in input_files:
file_label = os.path.splitext(os.path.basename(filepath))[0]
for flags in flag_combinations:
process_job(
label=file_label,
cmd_args=['-f', filepath, '-r', str(runs_r)],
flags=flags
)

# Model-based benchmarks
for model in models:
for flags in flag_combinations:
process_job(
label=model,
cmd_args=['-m', model, '-v', str(volume_v), '-r', str(runs_r)],
flags=flags
)
24 changes: 14 additions & 10 deletions scripts/latex_table.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
#!/usr/bin/env python3

import sys
import re
import argparse


# Function to format a number to two significant digits
def format_to_two_sig_digits(value):
if not isinstance(value, (int, float)) or value == 0:
return "N/A"

# Handle negative numbers
is_negative = value < 0
abs_value = abs(value)
Expand All @@ -27,14 +27,15 @@ def format_to_two_sig_digits(value):

# Format the number
if exponent >= 0 and exponent <= 4:
format = f"{'-' if is_negative else ''}{abs_value*10**exponent}"
format = f"{'-' if is_negative else ''}{abs_value * 10 ** exponent}"
format = format.replace(".0", "")
return format
elif exponent < 0 and exponent >= -4:
return f"{'-' if is_negative else ''}{abs_value*10**exponent:.1f}"
return f"{'-' if is_negative else ''}{abs_value * 10 ** exponent:.1f}"
else:
return f"{'-' if is_negative else ''}{abs_value:.1f}e{exponent}"


# Function to parse the raw input data
def parse_input(data):
lines = data.splitlines()
Expand All @@ -54,6 +55,7 @@ def parse_input(data):
parsed_data.append(current_entry)
if not current_entry:
continue

# Match lines with ns/f
match_ns = re.search(r"([\d.]+)\s*ns/f", line)
if match_ns and current_entry:
Expand All @@ -72,27 +74,29 @@ def parse_input(data):
# Filter out incomplete entries
return parsed_data


# Function to generate LaTeX table
def generate_latex_table(data):
def generate_latex_table(raw_input):
data = parse_input(raw_input)

latex_table = r"""
\begin{tabular}{lccc}
\toprule
\textbf{Name} & \textbf{ns/f} & \textbf{instructions/float} & \textbf{instructions/cycle} \\
\midrule
"""

for entry in data:
name = entry["name"].replace("_", "\\_") # Escape underscores for LaTeX
ns_per_float = format_to_two_sig_digits(entry['ns_per_float']) if 'ns_per_float' in entry else 'N/A'
inst_per_float = format_to_two_sig_digits(entry['inst_per_float']) if 'inst_per_float' in entry else 'N/A'
inst_per_cycle = format_to_two_sig_digits(entry['inst_per_cycle']) if 'inst_per_cycle' in entry else 'N/A'
latex_table += f"{name} & {ns_per_float} & {inst_per_float} & {inst_per_cycle} \\\\ \n"

latex_table += r"""\bottomrule
\end{tabular}
"""
return latex_table


if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Generate LaTeX table from performance data")
parser.add_argument("file", nargs="?", help="Optional input file name (if not provided, reads from stdin)")
Expand All @@ -111,6 +115,6 @@ def generate_latex_table(data):
sys.exit(1)
else:
raw_input = sys.stdin.read()
parsed_data = parse_input(raw_input)
latex_output = generate_latex_table(parsed_data)
print(latex_output)

latex_output = generate_latex_table(raw_input)
print(latex_output)