diff --git a/.gitignore b/.gitignore index 3d2059a..7b86ac3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,8 @@ build build_debug build_script +outputs +tags compile_commands.json .cache +**/__pycache__ diff --git a/benchmarks/algorithms.h b/benchmarks/algorithms.h index 074fc49..2bcd87a 100644 --- a/benchmarks/algorithms.h +++ b/benchmarks/algorithms.h @@ -217,6 +217,10 @@ int ryu(T d, std::span& buffer) { template int teju_jagua(T d, std::span& buffer) { + if(d == 0.0) { + std::copy_n("0E0", 3, buffer.data()); + return 3; + } const auto fields = teju::traits_t::teju(d); const bool sign = std::signbit(d); return to_chars(fields.mantissa, fields.exponent, sign, buffer.data()); diff --git a/benchmarks/benchmark.cpp b/benchmarks/benchmark.cpp index da6a2d0..db111ea 100644 --- a/benchmarks/benchmark.cpp +++ b/benchmarks/benchmark.cpp @@ -43,9 +43,10 @@ struct diy_float_t { template void process(const std::vector> &lines, const std::vector> &args, - const std::vector &algo_filter) { + const std::vector &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 parsed; for(const auto d : lines) { const auto v = jkj::grisu_exact(d.value); @@ -153,6 +154,8 @@ int main(int argc, char **argv) { cxxopts::value()->default_value("uniform")) ("s,single", "Use single precision instead of double.", cxxopts::value()->default_value("false")) + ("S,string-eval", "Evaluate perf. of string generation from decimal mantissa/exponent", + cxxopts::value()->default_value("false")) ("t,test", "Test the algorithms and find their properties.", cxxopts::value()->default_value("false")) ("e,errol", "Enable errol3 (current impl. returns invalid values, e.g., for 0).", @@ -204,14 +207,15 @@ int main(int argc, char **argv) { algorithms = initArgs(errol, repeat, fixed_size); const bool test = result["test"].as(); - std::visit([test, &filter](const auto &lines, const auto &args) { + const bool string_eval = result["string-eval"].as(); + std::visit([test, string_eval, &filter](const auto &lines, const auto &args) { using T1 = typename std::decay_t::value_type::Type; using T2 = typename std::decay_t::value_type::Type; if constexpr (std::is_same_v) { if (test) evaluateProperties(lines, args, filter); else - process(lines, args, filter); + process(lines, args, filter, string_eval); } }, numbers, algorithms); } catch (const std::exception &e) { diff --git a/benchmarks/random_generators.h b/benchmarks/random_generators.h index b1406b9..d6f37ac 100644 --- a/benchmarks/random_generators.h +++ b/benchmarks/random_generators.h @@ -2,6 +2,7 @@ #define RANDOM_GENERATORS_H #include +#include #include #include #include @@ -22,13 +23,42 @@ struct uniform_generator : float_number_generator { 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 +struct centered_generator : float_number_generator { + 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 dist_sign; + std::uniform_int_distribution dist_exp; // exclut 0=subnormal et max=inf/NaN + std::uniform_int_distribution 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(bits); + } +}; + template struct integer_uniform_generator : float_number_generator { std::random_device rd; @@ -37,8 +67,7 @@ struct integer_uniform_generator : float_number_generator { 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("]"); } @@ -47,9 +76,8 @@ struct integer_uniform_generator : float_number_generator { template struct simple_uniform : float_number_generator { - using gen_type = std::conditional_t; 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 { @@ -60,7 +88,6 @@ struct simple_uniform : float_number_generator { template struct simple_int : float_number_generator { - using gen_type = std::conditional_t; std::random_device rd; std::mt19937_64 gen; std::string describe() override { return "rand()"; } @@ -70,9 +97,8 @@ struct simple_int : float_number_generator { template struct one_over_rand : float_number_generator { - using gen_type = std::conditional_t; 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 { @@ -85,9 +111,10 @@ struct one_over_rand : float_number_generator { } }; -constexpr std::array model_names = { - "uniform", "integer_uniform", - "simple_uniform", "simple_int", +constexpr std::array model_names = { + "uniform_01" , "uniform_all" , "integer_uniform" , + "centered" , "non_centered" , + "simple_uniform" , "simple_int" , "one_over_rand" }; @@ -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>(new uniform_generator()); + if (name == "uniform_all") { + return std::unique_ptr>( + new uniform_generator(std::numeric_limits::lowest(), + std::numeric_limits::max()) + ); } - if (name == "integer_uniform") { + if (name == "centered") + return std::unique_ptr>(new centered_generator()); + if (name == "non_centered") + return std::unique_ptr>(new centered_generator()); + if (name == "integer_uniform") return std::unique_ptr>(new integer_uniform_generator()); - } - if (name == "simple_uniform") { + if (name == "simple_uniform") return std::unique_ptr>(new simple_uniform()); - } - if (name == "simple_int") { + if (name == "simple_int") return std::unique_ptr>(new simple_int()); - } - if (name == "one_over_rand") { + if (name == "one_over_rand") return std::unique_ptr>(new one_over_rand()); - } + 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>(new uniform_generator()); } diff --git a/scripts/generate_multiple_tables.py b/scripts/generate_multiple_tables.py new file mode 100755 index 0000000..efdd318 --- /dev/null +++ b/scripts/generate_multiple_tables.py @@ -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 + ) diff --git a/scripts/latex_table.py b/scripts/latex_table.py index ec16017..116120b 100755 --- a/scripts/latex_table.py +++ b/scripts/latex_table.py @@ -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) @@ -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() @@ -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: @@ -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)") @@ -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) \ No newline at end of file + + latex_output = generate_latex_table(raw_input) + print(latex_output)