/* ***************************************************************************** * %{QMAKE_PROJECT_NAME} * Copyright (c) %YEAR% killerbee * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * ****************************************************************************/ #include "kblib/poly_obj.h" #define CATCH_CONFIG_ENABLE_BENCHMARKING #include "catch.hpp" //#define FAST_TEST #if KBLIB_USE_CXX17 # ifndef FAST_TEST namespace { struct Base { virtual auto operator()() const noexcept -> unsigned = 0; Base() = default; Base(const Base&) = default; Base(Base&&) noexcept = default; virtual ~Base() = default; // this is used, but the compiler doesn't think so because it's in a .cpp // file [[maybe_unused]] constexpr static inline std::size_t max_derived_size = sizeof(unsigned); }; struct thrower final : Base { auto operator()() const noexcept -> unsigned override { return member; } unsigned member; thrower(unsigned i = 0) : member(i) {} thrower(const thrower&) = default; auto operator=(const thrower& other) & -> thrower& { check_throw(other.member); member = other.member; return *this; } private: static auto check_throw(unsigned v) -> void { if ((v & 7u) == 0) { throw 0; } } }; struct fptr { unsigned member; auto (*p)(const fptr*) noexcept -> unsigned; auto operator()() const noexcept -> unsigned { return p(this); } }; # define MAKE_DERIVED2(Number, Expr) \ auto fptr_d##Number(const fptr* p) noexcept->unsigned { \ const auto x = p->member; \ return Expr; \ } \ struct Derived##Number final : Base { \ auto operator()() const noexcept -> unsigned override { \ return Expr; \ } \ unsigned x; \ Derived##Number(unsigned i) \ : x(i) {} \ } # define MAKE_DERIVED(Number, Expr) MAKE_DERIVED2(Number, Expr) constexpr auto big_number = kblib::fnv::fnv_prime::value; MAKE_DERIVED(__COUNTER__, x); MAKE_DERIVED(__COUNTER__, x * 2); MAKE_DERIVED(__COUNTER__, x / 2); MAKE_DERIVED(__COUNTER__, ~x); MAKE_DERIVED(__COUNTER__, x + 42); MAKE_DERIVED(__COUNTER__, x * 2 + 42); MAKE_DERIVED(__COUNTER__, -x); MAKE_DERIVED(__COUNTER__, x - 42); MAKE_DERIVED(__COUNTER__, 42 - x); MAKE_DERIVED(__COUNTER__, (x * x)); MAKE_DERIVED(__COUNTER__, (x * x * 2)); MAKE_DERIVED(__COUNTER__, -x - 42); MAKE_DERIVED(__COUNTER__, x & 42); MAKE_DERIVED(__COUNTER__, x | 42); MAKE_DERIVED(__COUNTER__, x << 2); MAKE_DERIVED(__COUNTER__, x >> 2); MAKE_DERIVED(__COUNTER__, ~x << 2); MAKE_DERIVED(__COUNTER__, ~x >> 2); MAKE_DERIVED(__COUNTER__, (x >> 8) | (x << 24)); MAKE_DERIVED(__COUNTER__, (x >> 16) | (x << 16)); MAKE_DERIVED(__COUNTER__, (x >> 24) | (x << 8)); MAKE_DERIVED(__COUNTER__, (~x >> 8) | (~x << 24)); MAKE_DERIVED(__COUNTER__, (~x >> 16) | (~x << 16)); MAKE_DERIVED(__COUNTER__, (~x >> 24) | (~x << 8)); MAKE_DERIVED(__COUNTER__, (static_cast(x), 42)); MAKE_DERIVED(__COUNTER__, (x >> 16) | (~x << 16)); MAKE_DERIVED(__COUNTER__, (~x >> 16) | (x << 16)); MAKE_DERIVED(__COUNTER__, x ^ 42); MAKE_DERIVED(__COUNTER__, x ^ ((x >> 16) | (x << 16))); MAKE_DERIVED(__COUNTER__, -~x); MAKE_DERIVED(__COUNTER__, ~-x); MAKE_DERIVED(__COUNTER__, x * 42); MAKE_DERIVED(__COUNTER__, -x * 42); MAKE_DERIVED(__COUNTER__, -x / 42); MAKE_DERIVED(__COUNTER__, x % 42); MAKE_DERIVED(__COUNTER__, -(x % 42)); MAKE_DERIVED(__COUNTER__, (x * big_number)); MAKE_DERIVED(__COUNTER__, x ^ big_number); MAKE_DERIVED(__COUNTER__, x + big_number); MAKE_DERIVED(__COUNTER__, x - big_number); MAKE_DERIVED(__COUNTER__, big_number - x); MAKE_DERIVED(__COUNTER__, x / big_number); MAKE_DERIVED(__COUNTER__, x % big_number); MAKE_DERIVED(__COUNTER__, big_number / x); MAKE_DERIVED(__COUNTER__, big_number % x); MAKE_DERIVED(__COUNTER__, (void(x), big_number)); MAKE_DERIVED(__COUNTER__, x + (big_number >> 16)); MAKE_DERIVED(__COUNTER__, x + (big_number >> 24)); MAKE_DERIVED(__COUNTER__, (x << 16) + big_number); MAKE_DERIVED(__COUNTER__, (x << 16) - big_number); MAKE_DERIVED(__COUNTER__, big_number - (x << 16)); MAKE_DERIVED(__COUNTER__, (x << 16) ^ big_number); MAKE_DERIVED(__COUNTER__, (x << 16) / big_number); MAKE_DERIVED(__COUNTER__, big_number / (x << 16)); MAKE_DERIVED(__COUNTER__, (x << 16) & big_number); MAKE_DERIVED(__COUNTER__, (x << 16) + (big_number & 0x0000FFFF)); MAKE_DERIVED(__COUNTER__, (x << 16) % big_number); MAKE_DERIVED(__COUNTER__, big_number % (x << 16)); MAKE_DERIVED(__COUNTER__, x * 42 + big_number); MAKE_DERIVED(__COUNTER__, (x * (big_number % 42))); MAKE_DERIVED(__COUNTER__, (x * (-big_number % 42))); MAKE_DERIVED(__COUNTER__, (x * 14 + (big_number >> 16))); MAKE_DERIVED(__COUNTER__, (x * 14 - (big_number >> 16))); MAKE_DERIVED(__COUNTER__, (x * 14 ^ (big_number >> 16))); using variant_type = std::variant< Derived0, Derived1, Derived2, Derived3, Derived4, Derived5, Derived6, Derived7, Derived8, Derived9, Derived10, Derived11, Derived12, Derived13, Derived14, Derived15, Derived16, Derived17, Derived18, Derived19, Derived20, Derived21, Derived22, Derived23, Derived24, Derived25, Derived26, Derived27, Derived28, Derived29, Derived30, Derived31, Derived32, Derived33, Derived34, Derived35, Derived36, Derived37, Derived38, Derived39, Derived40, Derived41, Derived42, Derived43, Derived44, Derived45, Derived46, Derived47, Derived48, Derived49, Derived50, Derived51, Derived52, Derived53, Derived54, Derived55, Derived56, Derived57, Derived58, Derived59, Derived60, Derived61, Derived62, Derived63>; using variant_type_throws = std::variant< Derived0, Derived1, Derived2, Derived3, Derived4, Derived5, Derived6, Derived7, Derived8, Derived9, Derived10, Derived11, Derived12, Derived13, Derived14, Derived15, Derived16, Derived17, Derived18, Derived19, Derived20, Derived21, Derived22, Derived23, Derived24, Derived25, Derived26, Derived27, Derived28, Derived29, Derived30, Derived31, Derived32, Derived33, Derived34, Derived35, Derived36, Derived37, Derived38, Derived39, Derived40, Derived41, Derived42, Derived43, Derived44, Derived45, Derived46, Derived47, Derived48, Derived49, Derived50, Derived51, Derived52, Derived53, Derived54, Derived55, Derived56, Derived57, Derived58, Derived59, Derived60, Derived61, Derived62, Derived63, thrower>; constexpr std::array fptrs{ fptr_d0, fptr_d1, fptr_d2, fptr_d3, fptr_d4, fptr_d5, fptr_d6, fptr_d7, fptr_d8, fptr_d9, fptr_d10, fptr_d11, fptr_d12, fptr_d13, fptr_d14, fptr_d15, fptr_d16, fptr_d17, fptr_d18, fptr_d19, fptr_d20, fptr_d21, fptr_d22, fptr_d23, fptr_d24, fptr_d25, fptr_d26, fptr_d27, fptr_d28, fptr_d29, fptr_d30, fptr_d31, fptr_d32, fptr_d33, fptr_d34, fptr_d35, fptr_d36, fptr_d37, fptr_d38, fptr_d39, fptr_d40, fptr_d41, fptr_d42, fptr_d43, fptr_d44, fptr_d45, fptr_d46, fptr_d47, fptr_d48, fptr_d49, fptr_d50, fptr_d51, fptr_d52, fptr_d53, fptr_d54, fptr_d55, fptr_d56, fptr_d57, fptr_d58, fptr_d59, fptr_d60, fptr_d61, fptr_d62, fptr_d63, }; template auto make_fptr(unsigned v) noexcept -> fptr { switch (v % N) { case 0: return {v, &fptr_d1}; case 1: return {v, &fptr_d2}; case 2: return {v, &fptr_d3}; case 3: return {v, &fptr_d4}; default: __builtin_unreachable(); } return {v, fptrs[v % N]}; } template auto do_push_elem(std::index_sequence, T& d, unsigned v, F f) { const auto i = v % Num; ((i == Is and (d.emplace_back(f(kblib::constant{}, v)), true)) or ...); return; } template auto push_elem(T& d, unsigned v, F f) { return do_push_elem(std::make_index_sequence{}, d, v, f); }; template auto baseline_generic(std::vector... args) { return ( std::accumulate(args.begin(), args.end(), 0u, [](unsigned accum, const Ts& x) { return accum + x(); }) + ... + 0); } } // namespace TEST_CASE("poly_obj performance(4_old)") { const auto start = std::chrono::steady_clock::now(); # ifdef NDEBUG constexpr unsigned count = 1000; # else constexpr unsigned count = 100; # endif # ifdef SANITIZERS # define STR(s) # s std::cout << "Sanitizers active\n"; # endif std::vector> reproducibility_test; using poly_t = kblib::poly_obj< Base, sizeof(Derived1), kblib::poly_obj_traits>; auto push_checksum = [&](unsigned s, std::string_view name) { auto begin = reproducibility_test.begin(); auto end = reproducibility_test.end(); // Take only the first result for each run type if (std::find_if(begin, end, [&](const auto& p) { return p.second == name; }) == end) { reproducibility_test.emplace_back(s, name); } }; /* * Release / Debug, all times in microseconds: * * Four separate contiguous arrays * baseline: 5.377 / 37.549 * Polymorphism, always valid * raw pointer: 10.470 / 51.205 * unique_ptr: 11.826 / 70.346 * poly_obj: 14.644 / 79.075 * Type erasure, always valid * std::function: 15.247 / 106.562 * std::visit(v, f): 27.375 / 238.055 * kblib::visit(v, f...): 37.308 / 258.965 * kblib::visit(v)(f...): 37.253 / 288.355 * visit_indexed: 30.203 / 208.894 * kblib::visit2: 8.407 / 211.649 * kblib::visit2_nop: 8.650 / 222.502 * std::get_if: 8.580 / 158.164 * switch (v.index): 8.984 / 170.739 * Polymorphism, checking for invalid * raw pointer, ch: 15.859 / 56.917 * unique_ptr, ch: 18.344 / 108.046 * poly_obj, ch: 18.290 / 96.168 * Type erasure, checking for invalid * std::function, ch: 25.200 / 122.038 * kblib::visit2_nop, ch: 12.327 / 322.764 * std::get_if, ch: 12.386 / 232.006 * switch(v.index()), ch: 16.817 / 203.752 * Type erasure, exceptions for invalid * std::function, ex: 23.758 / 126.152 * std::visit(v, f), ex: 40.646 / 310.900 * kblib::visit(v, f...), ex: 44.016 / 328.484 * kblib::visit(v)(f...), ex: 46.345 / 377.681 * visit_indexed, ex: 40.066 / 261.261 * kblib::visit2, ex: 12.353 / 315.509 * * Overall test runtime: 80s / 286s * * Conclusions: * * In release builds, visit_indexed, std::function, and std::visit are very * slow, contiguous access is very fast, everything else is comparable to * each other, except that exceptions are, of course, very slow. * * In debug builds, type erasure seems to be extremely slow, enough even to * mostly drown out the exceptions, but the fastest is std::function by far. * visit2 is a bit faster in release builds, but poly_obj isn't much slower * and is much much faster than it in debug. In every instance, poly_obj * compares favorably to unique_ptr, which is good, because it is meant to * replace it in some uses. * * std::get_if and switch are predictably the fastest way of accessing a * variant in debug, but in release are tied with visit2. I do not think * that the messy and verbose code is justified by the debug performance * gain compared to the other options. * * If you want to use exceptions for invalid objects, kblib::visit2 is the * fastest way, followed by std::function. The others are equally twice as * slow. * * I cannot tell why visit_indexed is so much slower than visit2 in this * test. Their code is very similar. It must be the additional parameter * causing delays in setting up the call. * */ { std::vector d1; std::vector d2; std::vector d3; std::vector d4; kblib::FNV32_hash h; for (auto i : kblib::range(count)) { auto v = static_cast(h(i)); switch (v % 4) { case 0: d1.emplace_back(v); break; case 1: d2.emplace_back(v); break; case 2: d3.emplace_back(v); break; case 3: d4.emplace_back(v); } } BENCHMARK_ADVANCED("baseline")(Catch::Benchmark::Chronometer meter) { unsigned accum{}; meter.measure([&] { for (const auto& x : d1) { accum += x(); } for (const auto& x : d2) { accum += x(); } for (const auto& x : d3) { accum += x(); } for (const auto& x : d4) { accum += x(); } return accum; }); push_checksum(accum, "baseline"); }; BENCHMARK_ADVANCED("baseline_generic") (Catch::Benchmark::Chronometer meter) { unsigned accum{}; meter.measure([&] { accum = baseline_generic(d1, d2, d3, d4); }); push_checksum(accum, "baseline_generic"); }; } BENCHMARK_ADVANCED("raw pointer")(Catch::Benchmark::Chronometer meter) { std::vector d; kblib::FNV32_hash h; for (auto i : kblib::range(count)) { auto v = static_cast(h(i)); switch (v % 4) { case 0: d.push_back(new Derived0(v)); break; case 1: d.push_back(new Derived1(v)); break; case 2: d.push_back(new Derived2(v)); break; case 3: d.push_back(new Derived3(v)); } } unsigned accum{}; meter.measure([&] { for (auto x : d) { accum += (*x)(); } return accum; }); push_checksum(accum, "raw pointer"); for (auto x : d) { delete x; } }; BENCHMARK_ADVANCED("unique_ptr") (Catch::Benchmark::Chronometer meter) { std::vector> d; kblib::FNV32_hash h; for (auto i : kblib::range(count)) { auto v = h(i); switch (v % 4) { case 0: d.push_back(std::make_unique(v)); break; case 1: d.push_back(std::make_unique(v)); break; case 2: d.push_back(std::make_unique(v)); break; case 3: d.push_back(std::make_unique(v)); } } unsigned accum{}; meter.measure([&] { for (const auto& x : d) { accum += (*x)(); } return accum; }); push_checksum(accum, "unique pointer"); }; BENCHMARK_ADVANCED("poly_obj")(Catch::Benchmark::Chronometer meter) { std::vector d; kblib::FNV32_hash h; for (auto i : kblib::range(count)) { auto v = h(i); switch (v % 4) { case 0: d.push_back(poly_t::make(v)); break; case 1: d.push_back(poly_t::make(v)); break; case 2: d.push_back(poly_t::make(v)); break; case 3: d.push_back(poly_t::make(v)); } } unsigned accum{}; meter.measure([&] { for (const auto& x : d) { accum += x(); } return accum; }); push_checksum(accum, "poly_obj"); }; BENCHMARK_ADVANCED("function pointer")(Catch::Benchmark::Chronometer meter) { std::vector d; kblib::FNV32_hash h; for (auto i : kblib::range(count)) { auto v = static_cast(h(i)); d.push_back(make_fptr(v)); } unsigned accum{}; meter.measure([&] { for (auto x : d) { accum += x.p(&x); } return accum; }); push_checksum(accum, "raw pointer"); }; BENCHMARK_ADVANCED("function pointer (wrapped)") (Catch::Benchmark::Chronometer meter) { std::vector d; kblib::FNV32_hash h; for (auto i : kblib::range(count)) { auto v = static_cast(h(i)); d.push_back(make_fptr(v)); } unsigned accum{}; meter.measure([&] { for (auto x : d) { accum += x(); } return accum; }); push_checksum(accum, "raw pointer"); }; BENCHMARK_ADVANCED("std::function")(Catch::Benchmark::Chronometer meter) { std::vector> d; kblib::FNV32_hash h; for (auto i : kblib::range(count)) { auto v = static_cast(h(i)); switch (v % 4) { case 0: d.emplace_back(Derived0(v)); break; case 1: d.emplace_back(Derived1(v)); break; case 2: d.emplace_back(Derived2(v)); break; case 3: d.emplace_back(Derived3(v)); } } unsigned accum{}; meter.measure([&] { for (const auto& x : d) { accum += x(); } return accum; }); push_checksum(accum, "std::function"); }; { std::vector> d; kblib::FNV32_hash h; for (auto i : kblib::range(count)) { auto v = static_cast(h(i)); switch (v % 4) { case 0: d.emplace_back(Derived0(v)); break; case 1: d.emplace_back(Derived1(v)); break; case 2: d.emplace_back(Derived2(v)); break; case 3: d.emplace_back(Derived3(v)); break; } } BENCHMARK_ADVANCED("std::visit(v, f)") (Catch::Benchmark::Chronometer meter) { unsigned accum{}; meter.measure([&] { for (const auto& x : d) { accum += std::visit([](const auto& v) { return v(); }, x); } return accum; }); push_checksum(accum, "std::visit(v, f)"); }; BENCHMARK_ADVANCED("kblib::visit(v, f...)") (Catch::Benchmark::Chronometer meter) { unsigned accum{}; meter.measure([&] { for (const auto& x : d) { accum += kblib::visit(x, [](const auto& v) { return v(); }); } return accum; }); push_checksum(accum, "kblib::visit(v, f...)"); }; BENCHMARK_ADVANCED("kblib::visit(v)(f...)") (Catch::Benchmark::Chronometer meter) { unsigned accum{}; meter.measure([&] { for (const auto& x : d) { accum += kblib::visit(x)([](const auto& v) { return v(); }); } return accum; }); push_checksum(accum, "kblib::visit(v)(f...)"); }; BENCHMARK_ADVANCED("visit_indexed(v, f...)") (Catch::Benchmark::Chronometer meter) { unsigned accum{}; meter.measure([&] { for (const auto& x : d) { accum += kblib::visit_indexed( x, [](auto, const auto& v) { return v(); }); } return accum; }); push_checksum(accum, "visit_indexed(v, f...)"); }; BENCHMARK_ADVANCED("kblib::visit2(v, f...)") (Catch::Benchmark::Chronometer meter) { unsigned accum{}; meter.measure([&] { for (const auto& x : d) { accum += kblib::visit2(x, [](const auto& v) { return v(); }); } return accum; }); push_checksum(accum, "kblib::visit2(v, f...)"); }; BENCHMARK_ADVANCED("kblib::visit2_nop(v, f...)") (Catch::Benchmark::Chronometer meter) { unsigned accum{}; meter.measure([&] { for (const auto& x : d) { kblib::visit2_nop(x, [&](const auto& v) { accum += v(); }); } return accum; }); push_checksum(accum, "kblib::visit2_nop(v, f...)"); }; BENCHMARK_ADVANCED("std::get_if") (Catch::Benchmark::Chronometer meter) { unsigned accum{}; meter.measure([&] { for (const auto& x : d) { if (auto* const p = std::get_if<0>(&x)) { accum += (*p)(); } else if (auto* const p = std::get_if<1>(&x)) { accum += (*p)(); } else if (auto* const p = std::get_if<2>(&x)) { accum += (*p)(); } else if (auto* const p = std::get_if<3>(&x)) { accum += (*p)(); } } return accum; }); push_checksum(accum, "std::get_if"); }; BENCHMARK_ADVANCED("switch (v.index())") (Catch::Benchmark::Chronometer meter) { unsigned accum{}; meter.measure([&] { for (const auto& x : d) { switch (x.index()) { case 0: accum += std::get<0>(x)(); break; case 1: accum += std::get<1>(x)(); break; case 2: accum += std::get<2>(x)(); break; case 3: accum += std::get<3>(x)(); break; default:; } } return accum; }); push_checksum(accum, "switch (v.index())"); }; } // Test speed when some objects are invalid { BENCHMARK_ADVANCED("raw pointer, ch") (Catch::Benchmark::Chronometer meter) { std::vector d; kblib::FNV32_hash h; for (auto i : kblib::range(count)) { auto v = static_cast(h(i)); switch (v % 5) { case 0: d.emplace_back(new Derived0(v)); break; case 1: d.emplace_back(new Derived1(v)); break; case 2: d.emplace_back(new Derived2(v)); break; case 3: d.emplace_back(new Derived3(v)); break; case 4: try { d.push_back(new thrower(v)); } catch (int) { d.push_back(nullptr); } } } unsigned accum{}; meter.measure([&] { for (auto x : d) { if (x) { accum += (*x)(); } } return accum; }); for (auto x : d) { delete x; } }; BENCHMARK_ADVANCED("unique_ptr, ch") (Catch::Benchmark::Chronometer meter) { std::vector> d; kblib::FNV32_hash h; for (auto i : kblib::range(count)) { auto v = h(i); switch (v % 4) { case 0: d.push_back(std::make_unique(v)); break; case 1: d.push_back(std::make_unique(v)); break; case 2: d.push_back(std::make_unique(v)); break; case 3: d.push_back(std::make_unique(v)); break; case 4: try { d.push_back(std::make_unique(v)); } catch (int) { d.push_back(nullptr); } } } unsigned accum{}; meter.measure([&] { for (const auto& x : d) { if (x) { accum += (*x)(); } } return accum; }); }; BENCHMARK_ADVANCED("poly_obj, ch")(Catch::Benchmark::Chronometer meter) { std::vector d; kblib::FNV32_hash h; for (auto i : kblib::range(count)) { auto v = h(i); switch (v % 5) { case 0: d.push_back(poly_t::make(v)); break; case 1: d.push_back(poly_t::make(v)); break; case 2: d.push_back(poly_t::make(v)); break; case 3: d.push_back(poly_t::make(v)); break; case 4: try { d.push_back(poly_t::make(v)); } catch (int) { d.emplace_back(nullptr); } } } unsigned accum{}; meter.measure([&] { for (const auto& x : d) { if (x) { accum += x(); } } return accum; }); }; std::vector> df; std::vector> d; kblib::FNV32_hash h; for (auto i : kblib::range(count)) { auto v = static_cast(h(i)); switch (v % 5) { case 0: df.emplace_back(Derived0(v)); d.emplace_back(Derived0(v)); break; case 1: df.emplace_back(Derived1(v)); d.emplace_back(Derived1(v)); break; case 2: df.emplace_back(Derived2(v)); d.emplace_back(Derived2(v)); break; case 3: df.emplace_back(Derived3(v)); d.emplace_back(Derived3(v)); break; // Test speed of exception throwing and catching case 4: try { auto& b = df.emplace_back(thrower()); b = thrower(v); } catch (int) { } // These have to be done in separate try blocks because otherwise // df would throw and d wouldn't get pushed try { auto& b = d.emplace_back(std::in_place_type_t{}, 0); b = thrower(v); } catch (int) { } } } CHECK(d.size() == df.size()); CHECK(df.size() == count); CHECK(d.size() == count); BENCHMARK_ADVANCED("std::function, ch") (Catch::Benchmark::Chronometer meter) { unsigned accum{}; meter.measure([&] { for (const auto& x : df) { if (x) { accum += x(); } } return accum; }); }; BENCHMARK_ADVANCED("kblib::visit2_nop(v, f...), ch") (Catch::Benchmark::Chronometer meter) { unsigned accum{}; meter.measure([&] { for (const auto& x : d) { kblib::visit2_nop(x, [&](const auto& v) { accum += v(); }); } return accum; }); }; BENCHMARK_ADVANCED("std::get_if, ch") (Catch::Benchmark::Chronometer meter) { unsigned accum{}; meter.measure([&] { for (const auto& x : d) { if (auto* const p = std::get_if<0>(&x)) { accum += (*p)(); } else if (auto* const p = std::get_if<1>(&x)) { accum += (*p)(); } else if (auto* const p = std::get_if<2>(&x)) { accum += (*p)(); } else if (auto* const p = std::get_if<3>(&x)) { accum += (*p)(); } else if (auto* const p = std::get_if<4>(&x)) { accum += (*p)(); } } return accum; }); }; BENCHMARK_ADVANCED("switch (v.index()), ch") (Catch::Benchmark::Chronometer meter) { unsigned accum{}; meter.measure([&] { for (const auto& x : d) { switch (x.index()) { case 0: accum += std::get<0>(x)(); break; case 1: accum += std::get<1>(x)(); break; case 2: accum += std::get<2>(x)(); break; case 3: accum += std::get<3>(x)(); break; case 4: accum += std::get<4>(x)(); break; default:; } } return accum; }); }; BENCHMARK_ADVANCED("std::function, ex") (Catch::Benchmark::Chronometer meter) { unsigned accum{}; meter.measure([&] { for (const auto& x : df) { try { accum += x(); } catch (const std::bad_function_call&) { } } return accum; }); }; BENCHMARK_ADVANCED("std::visit(v, f), ex") (Catch::Benchmark::Chronometer meter) { unsigned accum{}; meter.measure([&] { for (const auto& x : d) { try { accum += std::visit([](const auto& v) { return v(); }, x); } catch (const std::bad_variant_access&) { } } return accum; }); }; BENCHMARK_ADVANCED("kblib::visit(v, f...), ex") (Catch::Benchmark::Chronometer meter) { unsigned accum{}; meter.measure([&] { for (const auto& x : d) { try { accum += kblib::visit(x, [](const auto& v) { return v(); }); } catch (const std::bad_variant_access&) { } } return accum; }); }; BENCHMARK_ADVANCED("kblib::visit(v)(f...), ex") (Catch::Benchmark::Chronometer meter) { unsigned accum{}; meter.measure([&] { for (const auto& x : d) { try { accum += kblib::visit(x)([](const auto& v) { return v(); }); } catch (const std::bad_variant_access&) { } } return accum; }); }; BENCHMARK_ADVANCED("visit_indexed(v, f...), ex") (Catch::Benchmark::Chronometer meter) { unsigned accum{}; meter.measure([&] { for (const auto& x : d) { try { accum += kblib::visit_indexed( x, [](auto, const auto& v) { return v(); }); } catch (const std::bad_variant_access&) { } } return accum; }); }; BENCHMARK_ADVANCED("kblib::visit2(v, f...), ex") (Catch::Benchmark::Chronometer meter) { unsigned accum{}; meter.measure([&] { for (const auto& x : d) { try { accum += kblib::visit2(x, [](const auto& v) { return v(); }); } catch (const std::bad_variant_access&) { } } return accum; }); }; } auto end = std::chrono::steady_clock::now(); std::chrono::duration> time = end - start; std::cout << "\n\nProfiling took " << time.count() << " seconds\n"; // All runs should produce identical results unsigned expected_value = reproducibility_test[0].first; std::cout << "Checksum: " << expected_value; for (auto i : kblib::range(std::size_t(1), reproducibility_test.size())) { const auto& run = reproducibility_test[i]; if (run.first != expected_value) { WARN(i << ": " << run.second << ": " << run.first << " != " << expected_value); } REQUIRE(run.first == expected_value); } } # endif // not defined(FAST_TEST) #endif // KBLIB_USE_CXX17