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
23 changes: 23 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,29 @@ if(libdecimal_BUILD_TESTS)
add_test_case(algebraic-identities)
endif()
# ------------------------------------------------------------------------------
# Benchmarks
# ------------------------------------------------------------------------------
option(libdecimal_BUILD_BENCHMARKS "Build benchmarks" OFF)

if(libdecimal_BUILD_BENCHMARKS)
set(BENCH_OUTPUT_DIR ${CMAKE_BINARY_DIR})
function(add_benchmark name)
add_executable(bench-${name} bench/${name}.cpp)
set_target_properties(bench-${name} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${BENCH_OUTPUT_DIR})
target_link_libraries(bench-${name} PRIVATE libdecimal::libdecimal libboostdecimal libnanobench)
target_compile_options(bench-${name} PRIVATE -O3 -march=native)
endfunction()

add_benchmark(construct-from-pair)
add_benchmark(decompose)
add_benchmark(compare)
add_benchmark(arithmetic)
add_benchmark(string)
add_benchmark(ops-fee)
add_benchmark(encode-decode)
add_benchmark(mixed-ops)
endif()
# ------------------------------------------------------------------------------
# MkDocs Documentation Targets
# ------------------------------------------------------------------------------
if(libdecimal_BUILD_DOCS)
Expand Down
99 changes: 99 additions & 0 deletions bench/arithmetic.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
#include <vector>
#include <boost/decimal.hpp>
#include <decimal/bid.hpp>
#include <decimal/bcd.hpp>
#include <decimal/fixed.hpp>
#include <decimal/scaled.hpp>

#include "utils.hpp"

#define ANKERL_NANOBENCH_IMPLEMENT
#include <nanobench.hpp>

namespace bench {

template<class significand_t>
std::vector<significand_t> make_int_input(std::size_t n) {
std::vector<significand_t> v;
v.reserve(n);
std::uint64_t seed = 0x9e3779b97f4a7c15ULL;
for (std::size_t i = 0; i < n; ++i) {
seed ^= seed >> 12, seed ^= seed << 25, seed ^= seed >> 27;
v.push_back(static_cast<significand_t>((seed * 2685821657736338717ULL) % 1'000'000ULL + 1ULL));
}
return v;
}

// Accumulator: sum all N values
template<class decimal_t, class make_fn>
void bench_accumulate(ankerl::nanobench::Bench& bench, std::string_view name, std::size_t n, make_fn make, decimal_t zero) {
const auto raw = make_int_input<std::uint64_t>(n);
std::vector<decimal_t> values;
values.reserve(n);
for (std::size_t i = 0; i < n; ++i) values.push_back(make(raw[i]));
bench.run(name.data(), [&] {
decimal_t acc = zero;
for (std::size_t i = 0; i < n; ++i) acc += values[i];
ankerl::nanobench::doNotOptimizeAway(acc);
});
}

// Dot product: sum of price * quantity pairs (N pairs)
template<class decimal_t, class make_fn>
void bench_dot_product(ankerl::nanobench::Bench& bench, std::string_view name, std::size_t n, make_fn make_price, make_fn make_qty, decimal_t zero) {
const auto prices = make_int_input<std::uint64_t>(n);
const auto qtys = make_int_input<std::uint64_t>(n);
std::vector<decimal_t> pv, qv;
pv.reserve(n); qv.reserve(n);
for (std::size_t i = 0; i < n; ++i) {
pv.push_back(make_price(prices[i]));
qv.push_back(make_qty(qtys[i]));
}
bench.run(name.data(), [&] {
decimal_t acc = zero;
for (std::size_t i = 0; i < n; ++i) acc += pv[i] * qv[i];
ankerl::nanobench::doNotOptimizeAway(acc);
});
}

} // namespace bench

int main() {
using bid64 = math::decimal_t<std::uint64_t>;
using bcd64 = math::bcd::decimal_t<std::uint64_t>;
using fixed64 = math::fixed::decimal_t<std::uint64_t, -4>;
using scaled = math::scaled::decimal_t<std::int64_t>;
using boost64 = boost::decimal::decimal64_t;
using cat = math::bid::category;

auto make_bid64 = [](std::uint64_t v){ return bid64(v, -4); };
auto make_bcd64 = [](std::uint64_t v){ return bcd64{cat::positive, v, std::int16_t{-4}}; };
auto make_fixed64 = [](std::uint64_t v){ return fixed64{cat::positive, v}; };
auto make_scaled = [](std::uint64_t v){ return scaled{static_cast<std::int64_t>(v), -4}; };
auto make_boost64 = [](std::uint64_t v){ return boost64(v, -4); };
auto make_double = [](std::uint64_t v){ return static_cast<double>(v) * 1e-4; };

constexpr std::size_t N_acc = 10'000;
constexpr std::size_t N_dot = 256;

{
ankerl::nanobench::Bench bench;
bench.title("accumulate sum (10k values)").relative(true).minEpochIterations(500).epochs(30);
bench::bench_accumulate<boost64>(bench, "boost decimal64", N_acc, make_boost64, boost64(0, 0));
bench::bench_accumulate<double> (bench, "double", N_acc, make_double, 0.0);
bench::bench_accumulate<bid64> (bench, "intel bid64", N_acc, make_bid64, bid64(0, 0));
bench::bench_accumulate<bcd64> (bench, "bcd64", N_acc, make_bcd64, bcd64{0ull});
bench::bench_accumulate<fixed64>(bench, "fixed64 -4", N_acc, make_fixed64, fixed64{cat::zero, 0u});
bench::bench_accumulate<scaled> (bench, "scaled int64", N_acc, make_scaled, scaled{0, 0});
}
{
ankerl::nanobench::Bench bench;
bench.title("dot product price*qty (256 pairs)").relative(true).minEpochIterations(2'000).epochs(30);
bench::bench_dot_product<boost64>(bench, "boost decimal64", N_dot, make_boost64, make_boost64, boost64(0, 0));
bench::bench_dot_product<double> (bench, "double", N_dot, make_double, make_double, 0.0);
bench::bench_dot_product<bid64> (bench, "intel bid64", N_dot, make_bid64, make_bid64, bid64(0, 0));
bench::bench_dot_product<bcd64> (bench, "bcd64", N_dot, make_bcd64, make_bcd64, bcd64{0ull});
bench::bench_dot_product<fixed64>(bench, "fixed64 -4", N_dot, make_fixed64, make_fixed64, fixed64{cat::zero, 0u});
bench::bench_dot_product<scaled> (bench, "scaled int64", N_dot, make_scaled, make_scaled, scaled{0, 0});
}
}
82 changes: 82 additions & 0 deletions bench/compare.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#include <algorithm>
#include <vector>
#include <boost/decimal.hpp>
#include <decimal/bid.hpp>
#include <decimal/fixed.hpp>
#include <decimal/scaled.hpp>

#include "utils.hpp"

#define ANKERL_NANOBENCH_IMPLEMENT
#include <nanobench.hpp>

namespace bench {

template<class significand_t>
std::vector<significand_t> make_int_input(std::size_t n) {
std::vector<significand_t> v;
v.reserve(n);
std::uint64_t seed = 0xdeadbeefcafe1234ULL;
for (std::size_t i = 0; i < n; ++i) {
seed ^= seed >> 12, seed ^= seed << 25, seed ^= seed >> 27;
v.push_back(static_cast<significand_t>((seed * 2685821657736338717ULL) % 1'000'000'000ULL + 1ULL));
}
return v;
}

template<class decimal_t, class make_fn>
void bench_compare_micro(ankerl::nanobench::Bench& bench, std::string_view name, std::size_t n, make_fn make) {
const auto raw = make_int_input<std::uint64_t>(n);
std::vector<decimal_t> values;
values.reserve(n);
for (std::size_t i = 0; i < n; ++i) values.push_back(make(raw[i]));
std::size_t count = 0;
bench.run(name.data(), [&] {
for (std::size_t i = 0; i + 1 < n; ++i)
if (values[i] < values[i + 1]) ++count;
ankerl::nanobench::doNotOptimizeAway(count);
});
}

template<class decimal_t, class make_fn>
void bench_sort(ankerl::nanobench::Bench& bench, std::string_view name, std::size_t n, make_fn make) {
const auto raw = make_int_input<std::uint64_t>(n);
std::vector<decimal_t> values;
values.reserve(n);
for (std::size_t i = 0; i < n; ++i) values.push_back(make(raw[i]));
bench.run(name.data(), [&] {
std::vector<decimal_t> tmp = values;
std::sort(tmp.begin(), tmp.end());
ankerl::nanobench::doNotOptimizeAway(tmp.front());
});
}

} // namespace bench

int main() {
using bid64 = math::decimal_t<std::uint64_t>;
using fixed64 = math::fixed::decimal_t<std::uint64_t, -4>;
using scaled = math::scaled::decimal_t<std::int64_t>;
using boost64 = boost::decimal::decimal64_t;
constexpr std::size_t N_micro = 10'000;
constexpr std::size_t N_sort = 1'000;

{
ankerl::nanobench::Bench bench;
bench.title("compare < (micro, 10k pairs)").relative(true).minEpochIterations(5'000).epochs(30);
bench::bench_compare_micro<boost64>(bench, "boost decimal64",N_micro, [](std::uint64_t v){ return boost64(v, -4); });
bench::bench_compare_micro<double> (bench, "double", N_micro, [](std::uint64_t v){ return static_cast<double>(v) * 1e-4; });
bench::bench_compare_micro<bid64> (bench, "intel bid64", N_micro, [](std::uint64_t v){ return bid64(v, -4); });
bench::bench_compare_micro<fixed64>(bench, "fixed64 -4", N_micro, [](std::uint64_t v){ return fixed64{math::bid::category::positive, static_cast<std::uint64_t>(v)}; });
bench::bench_compare_micro<scaled> (bench, "scaled int64", N_micro, [](std::uint64_t v){ return scaled{static_cast<std::int64_t>(v), -4}; });
}
{
ankerl::nanobench::Bench bench;
bench.title("sort 1000 values").relative(true).minEpochIterations(500).epochs(30);
bench::bench_sort<boost64>(bench, "boost decimal64",N_sort, [](std::uint64_t v){ return boost64(v, -4); });
bench::bench_sort<double> (bench, "double", N_sort, [](std::uint64_t v){ return static_cast<double>(v) * 1e-4; });
bench::bench_sort<bid64> (bench, "intel bid64", N_sort, [](std::uint64_t v){ return bid64(v, -4); });
bench::bench_sort<fixed64>(bench, "fixed64 -4", N_sort, [](std::uint64_t v){ return fixed64{math::bid::category::positive, static_cast<std::uint64_t>(v)}; });
bench::bench_sort<scaled> (bench, "scaled int64", N_sort, [](std::uint64_t v){ return scaled{static_cast<std::int64_t>(v), -4}; });
}
}
82 changes: 82 additions & 0 deletions bench/construct-from-pair.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#include <boost/decimal.hpp>
#include <decimal/bid.hpp>
#include <decimal/utils.hpp>

#include "traits.hpp"
#include "utils.hpp"

#define ANKERL_NANOBENCH_IMPLEMENT
#include <nanobench.hpp>

namespace bench {
using import_32_t = std::tuple<
traits_t<boost::decimal::decimal32_t>,
bench::ieee754<float, bench::kind_t::naive>,
bench::ieee754<float, bench::kind_t::optimised>,
traits_t<math::decimal_t<std::uint32_t>>,
traits_t<math::scaled::decimal_t<std::uint32_t>>,
traits_t<math::bcd::decimal_t<std::uint32_t>>,
bench::fixed<math::fixed::decimal_t<std::uint32_t, -4>, bench::kind_t::naive>,
bench::fixed<math::fixed::decimal_t<std::uint32_t, -4>, bench::kind_t::optimised>
>;

using import_64_t = std::tuple<
traits_t<boost::decimal::decimal64_t>,
bench::ieee754<double, bench::kind_t::naive>,
bench::ieee754<double, bench::kind_t::optimised>,
traits_t<math::decimal_t<std::uint64_t>>,
traits_t<math::scaled::decimal_t<std::uint64_t>>,
traits_t<math::bcd::decimal_t<std::uint64_t>>,
bench::fixed<math::fixed::decimal_t<std::uint64_t, -4>, bench::kind_t::naive>,
bench::fixed<math::fixed::decimal_t<std::uint64_t, -4>, bench::kind_t::optimised>
>;

using type_list = std::tuple<import_32_t, import_64_t>;


template<class significand_t> struct input_t {
significand_t significand;
int exponent;
};

template<class significand_t> std::vector<input_t<significand_t>> make_input(std::size_t n, int exponent = -4) {
std::vector<input_t<significand_t>> v;
v.reserve(n);
std::uint64_t seed = 0x9e3779b97f4a7c15ULL;
for (std::size_t i = 0; i < n; ++i) {
seed ^= seed >> 12, seed ^= seed << 25, seed ^= seed >> 27;
std::uint64_t raw = (seed * 2685821657736338717ULL) % 1'000'000'000ULL + 1ULL;
v.push_back({static_cast<significand_t>(raw), exponent });
}
return v;
}

template<class trait_t>
void run_import_case(ankerl::nanobench::Bench& bench, std::size_t n) {
using significand_t = typename trait_t::significand_t;
const auto input = make_input<significand_t>(n);
bench.run(trait_t::name().data(), [&] {
for (std::size_t i = 0; i < n; ++i) {
auto x = trait_t::make(input[i].significand, input[i].exponent);
ankerl::nanobench::doNotOptimizeAway(x);
}
});
}

template<class group> void run_import_group(std::string_view title, std::size_t n) {
ankerl::nanobench::Bench bench;
bench.title(title.data()).relative(true)
.minEpochIterations(50'000).epochs(30);
static_for<group>([&]<class element_t>() {
run_import_case<element_t>(bench, n);
});
}

} // namespace bench

int main() {
constexpr std::size_t n = 1'000;

bench::run_import_group<bench::import_32_t>("construct from significand+exponent (32-bit)", n);
bench::run_import_group<bench::import_64_t>("construct from significand+exponent (64-bit)", n);
}
Loading
Loading