File: benchmark.cpp

package info (click to toggle)
dragonbox 1.1.3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 5,148 kB
  • sloc: cpp: 8,752; ansic: 1,522; makefile: 18
file content (238 lines) | stat: -rw-r--r-- 9,772 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
// Copyright 2020-2021 Junekey Jeon
//
// The contents of this file may be used under the terms of
// the Apache License v2.0 with LLVM Exceptions.
//
//    (See accompanying file LICENSE-Apache or copy at
//     https://llvm.org/foundation/relicensing/LICENSE.txt)
//
// Alternatively, the contents of this file may be used under the terms of
// the Boost Software License, Version 1.0.
//    (See accompanying file LICENSE-Boost or copy at
//     https://www.boost.org/LICENSE_1_0.txt)
//
// Unless required by applicable law or agreed to in writing, this software
// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.

#include "benchmark.h"
#include "random_float.h"
#include "dragonbox/dragonbox_to_chars.h"
#include <array>
#include <chrono>
#include <cstring>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <string_view>
#include <utility>
#include <unordered_map>
#include <vector>

template <class Float>
class benchmark_holder {
public:
    static constexpr auto max_digits = std::size_t(std::numeric_limits<Float>::max_digits10);

    static benchmark_holder& get_instance() {
        static benchmark_holder<Float> inst;
        return inst;
    }

    // Generate random samples
    void prepare_samples(std::size_t number_of_general_samples,
                         std::size_t number_of_digits_samples_per_digits) {
        samples_[0].resize(number_of_general_samples);
        for (auto& sample : samples_[0])
            sample = uniformly_randomly_generate_general_float<Float>(rg_);

        for (unsigned int digits = 1; digits <= max_digits; ++digits) {
            samples_[digits].resize(number_of_digits_samples_per_digits);
            for (auto& sample : samples_[digits])
                sample = randomly_generate_float_with_given_digits<Float>(digits, rg_);
        }
    }

    // { "name" : [(digits, [(sample, measured_time)])] }
    // Results for general samples is stored at the position digits=0
    using output_type =
        std::unordered_map<std::string,
                           std::array<std::vector<std::pair<Float, double>>, max_digits + 1>>;
    void run(std::size_t number_of_iterations, std::string_view float_name, output_type& out) {
        assert(number_of_iterations >= 1);
        char buffer[40];

        for (auto const& name_func_pair : name_func_pairs_) {
            auto [result_array_itr, is_inserted] = out.insert_or_assign(
                name_func_pair.first,
                std::array<std::vector<std::pair<Float, double>>, max_digits + 1>{});

            for (unsigned int digits = 0; digits <= max_digits; ++digits) {
                (*result_array_itr).second[digits].resize(samples_[digits].size());
                auto out_itr = (*result_array_itr).second[digits].begin();

                if (digits == 0) {
                    std::cout << "Benchmarking " << name_func_pair.first
                              << " with uniformly random " << float_name << "'s...\n";
                }
                else {
                    std::cout << "Benchmarking " << name_func_pair.first
                              << " with (approximately) uniformly random " << float_name << "'s of "
                              << digits << " digits...\n";
                }

                for (Float sample : samples_[digits]) {
                    auto from = std::chrono::high_resolution_clock::now();
                    for (std::size_t i = 0; i < number_of_iterations; ++i) {
                        name_func_pair.second(sample, buffer);
                    }
                    auto dur = std::chrono::high_resolution_clock::now() - from;

                    *out_itr = {
                        sample,
                        double(std::chrono::duration_cast<std::chrono::nanoseconds>(dur).count()) /
                            double(number_of_iterations)};
                    ++out_itr;
                }
            }
        }
    }

    output_type run(std::size_t number_of_iterations, std::string_view float_name) {
        output_type out;
        run(number_of_iterations, float_name, out);
        return out;
    }

    void register_function(std::string_view name, void (*func)(Float, char*)) {
        name_func_pairs_.emplace(name, func);
    }

private:
    benchmark_holder() : rg_(generate_correctly_seeded_mt19937_64()) {}

    // Digits samples for [1] ~ [max_digits], general samples for [0]
    std::array<std::vector<Float>, max_digits + 1> samples_;
    std::mt19937_64 rg_;
    std::unordered_map<std::string, void (*)(Float, char*)> name_func_pairs_;
};

register_function_for_benchmark::register_function_for_benchmark(std::string_view name,
                                                                 void (*func_float)(float, char*)) {
    benchmark_holder<float>::get_instance().register_function(name, func_float);
};

register_function_for_benchmark::register_function_for_benchmark(std::string_view name,
                                                                 void (*func_double)(double,
                                                                                     char*)) {
    benchmark_holder<double>::get_instance().register_function(name, func_double);
};

register_function_for_benchmark::register_function_for_benchmark(
    std::string_view name, void (*func_float)(float, char*), void (*func_double)(double, char*)) {
    benchmark_holder<float>::get_instance().register_function(name, func_float);
    benchmark_holder<double>::get_instance().register_function(name, func_double);
};


#define RUN_MATLAB
#ifdef RUN_MATLAB
    #include <cstdlib>

void run_matlab() {
    struct launcher {
        ~launcher() { std::system("matlab -nosplash -r \"cd('matlab'); plot_benchmarks\""); }
    };
    static launcher l;
}
#endif

template <class Float>
static void benchmark_test(std::string_view float_name, std::size_t number_of_uniform_samples,
                           std::size_t number_of_digits_samples_per_digits,
                           std::size_t number_of_iterations) {
    auto& inst = benchmark_holder<Float>::get_instance();
    std::cout << "Generating random samples...\n";
    inst.prepare_samples(number_of_uniform_samples, number_of_digits_samples_per_digits);
    auto out = inst.run(number_of_iterations, float_name);

    std::cout << "Benchmarking done.\n"
              << "Now writing to files...\n";

    // Write uniform benchmark results
    auto filename = std::string("results/uniform_benchmark_");
    filename += float_name;
    filename += ".csv";
    std::ofstream out_file{filename};
    out_file << "number_of_samples," << number_of_uniform_samples << std::endl;
    ;
    out_file << "name,sample,bit_representation,time\n";

    char buffer[64];
    typename jkj::dragonbox::default_float_traits<Float>::carrier_uint br;
    for (auto& name_result_pair : out) {
        for (auto const& data_time_pair : name_result_pair.second[0]) {
            std::memcpy(&br, &data_time_pair.first, sizeof(Float));
            jkj::dragonbox::to_chars(data_time_pair.first, buffer);
            out_file << "\"" << name_result_pair.first << "\"," << buffer << ","
                     << "0x" << std::hex << std::setfill('0');
            if constexpr (sizeof(Float) == 4)
                out_file << std::setw(8);
            else
                out_file << std::setw(16);
            out_file << br << std::dec << "," << data_time_pair.second << "\n";
        }
    }
    out_file.close();

    // Write digits benchmark results
    filename = std::string("results/digits_benchmark_");
    filename += float_name;
    filename += ".csv";
    out_file.open(filename);
    out_file << "number_of_samples_per_digits," << number_of_digits_samples_per_digits << std::endl;
    ;
    out_file << "name,digits,sample,time\n";

    for (auto& name_result_pair : out) {
        for (unsigned int digits = 1; digits <= benchmark_holder<Float>::max_digits; ++digits) {
            for (auto const& data_time_pair : name_result_pair.second[digits]) {
                jkj::dragonbox::to_chars(data_time_pair.first, buffer);
                out_file << "\"" << name_result_pair.first << "\"," << digits << "," << buffer
                         << "," << data_time_pair.second << "\n";
            }
        }
    }
    out_file.close();
}

int main() {
    constexpr bool benchmark_float = true;
    constexpr std::size_t number_of_uniform_benchmark_samples_float = 1000000;
    constexpr std::size_t number_of_digits_benchmark_samples_per_digits_float = 100000;
    constexpr std::size_t number_of_benchmark_iterations_float = 1000;

    constexpr bool benchmark_double = true;
    constexpr std::size_t number_of_uniform_benchmark_samples_double = 1000000;
    constexpr std::size_t number_of_digits_benchmark_samples_per_digits_double = 100000;
    constexpr std::size_t number_of_benchmark_iterations_double = 1000;

    if constexpr (benchmark_float) {
        std::cout << "[Running benchmark for binary32...]\n";
        benchmark_test<float>("binary32", number_of_uniform_benchmark_samples_float,
                              number_of_digits_benchmark_samples_per_digits_float,
                              number_of_benchmark_iterations_float);
        std::cout << "Done.\n\n\n";
    }
    if constexpr (benchmark_double) {
        std::cout << "[Running benchmark for binary64...]\n";
        benchmark_test<double>("binary64", number_of_uniform_benchmark_samples_double,
                               number_of_digits_benchmark_samples_per_digits_double,
                               number_of_benchmark_iterations_double);
        std::cout << "Done.\n\n\n";
    }

#ifdef RUN_MATLAB
    run_matlab();
#endif
}