814 lines
38 KiB
C++
814 lines
38 KiB
C++
/* @file bplustree.cpp */
|
|
|
|
#define CATCH_CONFIG_ENABLE_BENCHMARKING
|
|
|
|
#include "random_tree_ops.hpp"
|
|
#include "xo/ordinaltree/BplusTree.hpp"
|
|
#include "xo/randomgen/random_seed.hpp"
|
|
#include "xo/randomgen/print.hpp"
|
|
#include "indentlog/scope.hpp"
|
|
#include "catch2/catch.hpp"
|
|
|
|
namespace {
|
|
using xo::tree::BplusTree;
|
|
using xo::tree::BplusStdProperties;
|
|
using xo::tree::NullReduce;
|
|
using xo::tree::Machdep;
|
|
|
|
using xo::rng::Seed;
|
|
|
|
using utest::TreeUtil;
|
|
|
|
using xo::scope;
|
|
using xo::scope_setup;
|
|
using xo::xtag;
|
|
|
|
using BtreeKey = int;
|
|
using BtreeValue = double;
|
|
using BtreeProperties = BplusStdProperties<BtreeKey, BtreeValue, xo::tree::tags::ordinal_enabled>;
|
|
//using BtreeProperties = BplusStdProperties<BtreeKey, BtreeValue, xo::tree::tags::ordinal_disabled>;
|
|
using BpTree = BplusTree<BtreeKey,
|
|
BtreeValue,
|
|
NullReduce<BtreeKey>,
|
|
BtreeProperties>;
|
|
|
|
/* random test data (e.g. permutation of integers [0 .. n-1]).
|
|
* will do various tree operations using these permutations to control order
|
|
* in which keys are presented
|
|
*/
|
|
struct RandomTestData {
|
|
RandomTestData(std::size_t n,
|
|
xo::rng::xoshiro256ss * p_rgen);
|
|
|
|
std::vector<std::uint32_t> const & u1v() const { return u1v_; }
|
|
std::vector<std::uint32_t> const & u2v() const { return u2v_; }
|
|
std::vector<std::uint32_t> const & u12_v() const { return u12_v_; }
|
|
|
|
private:
|
|
/* a set comprising n randomly chosen elements drawn from [0 .. 2n-1].
|
|
* here n = .u1v.size = .u2v.size
|
|
*/
|
|
std::vector<std::uint32_t> u1v_;
|
|
/* complement of .u1v w.r.t. [0 .. 2n-1] */
|
|
std::vector<std::uint32_t> u2v_;
|
|
/* .u1v + .u2v */
|
|
std::vector<std::uint32_t> u12_v_;
|
|
}; /*RandomTestData*/
|
|
|
|
RandomTestData::RandomTestData(std::size_t n,
|
|
xo::rng::xoshiro256ss * p_rgen)
|
|
: u1v_(n), u2v_(n), u12_v_(2*n)
|
|
{
|
|
/* permutation of [0 .. 2n-1] */
|
|
std::vector<std::uint32_t> u(2*n);
|
|
|
|
for (std::uint32_t i=0; i<2*n; ++i)
|
|
u[i] = i;
|
|
std::shuffle(u.begin(), u.end(), *p_rgen);
|
|
|
|
u1v_ = std::vector<std::uint32_t>(u.begin(), u.begin() + n);
|
|
u2v_ = std::vector<std::uint32_t>(u.begin() + n, u.end());
|
|
u12_v_ = std::move(u);
|
|
} /*ctor*/
|
|
|
|
/* representation-independent feature benchmarks for tree algorithms.
|
|
*
|
|
* +------------------+
|
|
* |AbstractTestParams|
|
|
* +------------------+
|
|
* ^
|
|
* | isa +----------------+
|
|
* +------------|StdMapTestParams| benchmark std::map<BtreeKey, BtreeValue> (bogey!)
|
|
* | +----------------+
|
|
* |
|
|
* | isa +---------------+
|
|
* +------------|BtreeTestParams| benchmark BplusTree<BtreeKey, BtreeValue, ..>
|
|
* +---------------+
|
|
*/
|
|
struct AbstractTestParams {
|
|
virtual ~AbstractTestParams() = default;
|
|
/* insert benchmark:
|
|
* 1. prime tree by inserting RandomTestData.u1v (random subset comprising n draws from [0 .. 2n-1])
|
|
* 2. measure cost of inserting RandomTestData.u2v (complement of u1v w.r.t [0 .. 2n-1])
|
|
*/
|
|
virtual void run_insert_benchmark(RandomTestData const & random_testdata) const = 0;
|
|
virtual void run_erase_benchmark(RandomTestData const & random_testdata) const = 0;
|
|
virtual void run_lookup_benchmark(RandomTestData const & random_testdata) const = 0;
|
|
virtual void run_traverse_benchmark(RandomTestData const & random_testdata) const = 0;
|
|
};
|
|
|
|
struct StdMapTestParams : public AbstractTestParams {
|
|
StdMapTestParams(char const * name)
|
|
: test_name_{name} {}
|
|
|
|
/* 1. make map containing keys in random_testdata.u1v.
|
|
* 2. during construction, interleave inserts against a temporary map,
|
|
* to spoil sequential heap allocation (i.e. simulate fragmentation)
|
|
*/
|
|
std::map<BtreeKey, BtreeValue> make_random_map1(RandomTestData const & random_testdata) const {
|
|
std::map<BtreeKey, BtreeValue> tree;
|
|
/* 2nd tree to interfere with locality */
|
|
std::map<BtreeKey, BtreeValue> tree2;
|
|
|
|
for (std::uint32_t x : random_testdata.u1v()) {
|
|
tree.insert({x, 10*x});
|
|
/* 2nd tree to interfere with locality */
|
|
for (std::uint32_t y = 0; y < 8; ++y)
|
|
tree2.insert({8*x+y, 10*8*x+y});
|
|
}
|
|
|
|
return tree;
|
|
} /*make_random_map1*/
|
|
|
|
/* 1. make map containing keys in both random_testdata.u1v + random_testdata.u2v
|
|
* 2. during construction, interleave inserts against a temporary map,
|
|
* to spoil sequential heap allocation (i.e. simulate fragmentation)
|
|
*/
|
|
std::map<BtreeKey, BtreeValue> make_random_map12(RandomTestData const & random_testdata) const {
|
|
std::map<BtreeKey, BtreeValue> tree;
|
|
/* temporary tree to interfere with locality */
|
|
std::map<BtreeKey, BtreeValue> tree2;
|
|
|
|
for (std::uint32_t x : random_testdata.u12_v()) {
|
|
tree.insert({x, 10*x});
|
|
/* 2nd tree to interfere with memory locality */
|
|
for (std::uint32_t y = 0; y < 8; ++y)
|
|
tree2.insert({8*x+y, 10*8*x+y});
|
|
}
|
|
|
|
return tree;
|
|
} /*make_random_map12*/
|
|
|
|
virtual void run_insert_benchmark(RandomTestData const & random_testdata) const override;
|
|
virtual void run_erase_benchmark(RandomTestData const & random_testdata) const override;
|
|
virtual void run_lookup_benchmark(RandomTestData const & random_testdata) const override;
|
|
virtual void run_traverse_benchmark(RandomTestData const & random_testdata) const override;
|
|
|
|
char const * test_name_ = nullptr;
|
|
};
|
|
|
|
void
|
|
StdMapTestParams::run_insert_benchmark(RandomTestData const & random_testdata) const
|
|
{
|
|
/* see also: BtreeTestParams::run_insert_benchmark() */
|
|
|
|
BENCHMARK_ADVANCED(this->test_name_)(Catch::Benchmark::Chronometer clock)
|
|
{
|
|
std::size_t n = random_testdata.u1v().size();
|
|
|
|
std::map<BtreeKey, BtreeValue> tree
|
|
= std::move(this->make_random_map1(random_testdata));
|
|
|
|
/* benchmark additional inserts */
|
|
clock.measure([&](int seq) {
|
|
std::size_t key = random_testdata.u2v()[seq % n];
|
|
double value = 10 * key;
|
|
|
|
tree.insert({key, value});
|
|
return tree.size();
|
|
});
|
|
};
|
|
} /*run_insert_benchmark*/
|
|
|
|
void
|
|
StdMapTestParams::run_erase_benchmark(RandomTestData const & random_testdata) const
|
|
{
|
|
BENCHMARK_ADVANCED(this->test_name_)(Catch::Benchmark::Chronometer clock)
|
|
{
|
|
std::size_t n = random_testdata.u1v().size();
|
|
|
|
std::map<BtreeKey, BtreeValue> tree
|
|
= std::move(this->make_random_map12(random_testdata));;
|
|
|
|
clock.measure([&](int seq) {
|
|
/* catch2 decides how many times to run this lambda,
|
|
* in effort to get statistically valid sample.
|
|
*
|
|
* If it calls lambda n times, then seq will increase from [0 .. n-1]
|
|
*/
|
|
|
|
std::size_t key = random_testdata.u1v()[seq % n];
|
|
|
|
//std::clog << "i=" << i << std::endl;
|
|
tree.erase(key);
|
|
|
|
return tree.size();
|
|
});
|
|
};
|
|
} /*run_erase_benchmark*/
|
|
|
|
void
|
|
StdMapTestParams::run_lookup_benchmark(RandomTestData const & random_testdata) const
|
|
{
|
|
BENCHMARK_ADVANCED(this->test_name_)(Catch::Benchmark::Chronometer clock)
|
|
{
|
|
std::size_t n = random_testdata.u1v().size();
|
|
|
|
std::map<BtreeKey, BtreeValue> tree
|
|
= std::move(this->make_random_map1(random_testdata));
|
|
|
|
clock.measure([&](int seq) {
|
|
/* catch2 decides how many times to run this lambda,
|
|
* in effort to get statistically valid sample.
|
|
*
|
|
* If it calls lambda n times, then seq will increase from [0 .. n-1]
|
|
*/
|
|
|
|
std::size_t key = random_testdata.u1v()[seq % n];
|
|
|
|
//std::clog << "i=" << i << std::endl;
|
|
double value = tree[key];
|
|
|
|
return value;
|
|
});
|
|
};
|
|
} /*run_lookup_benchmark*/
|
|
|
|
void
|
|
StdMapTestParams::run_traverse_benchmark(RandomTestData const & random_testdata) const
|
|
{
|
|
BENCHMARK_ADVANCED(this->test_name_)(Catch::Benchmark::Chronometer clock)
|
|
{
|
|
std::size_t n = random_testdata.u1v().size();
|
|
|
|
std::map<BtreeKey, BtreeValue> tree
|
|
= std::move(this->make_random_map1(random_testdata));
|
|
|
|
clock.measure([&](int seq) {
|
|
/* catch2 decides how many times to run this lambda,
|
|
* in effort to get statistically valid sample.
|
|
*
|
|
* If it calls lambda n times, then seq will increase from [0 .. n-1]
|
|
*/
|
|
|
|
std::size_t key = random_testdata.u1v()[seq % n];
|
|
|
|
//std::clog << "i=" << i << std::endl;
|
|
double value = tree[key];
|
|
|
|
return value;
|
|
});
|
|
};
|
|
} /*run_traverse_benchmark*/
|
|
|
|
struct BtreeTestParams : public AbstractTestParams {
|
|
BtreeTestParams(char const * name, std::size_t bf, bool debug_flag)
|
|
: test_name_{name}, branching_factor_{bf}, debug_flag_{debug_flag} {}
|
|
|
|
BpTree make_empty_bptree() const {
|
|
BtreeProperties properties(branching_factor_,
|
|
debug_flag_);
|
|
return BpTree(properties);
|
|
}
|
|
|
|
/* 1. make b+ tree containing keys in random_testdata.u1v.
|
|
* 2. during constructions, interleave inserts against a temporary b+ tree,
|
|
* to spoil sequential heap allocation (i.e. simulate fragmentation)
|
|
*/
|
|
BpTree make_random_bptree1(RandomTestData const & random_testdata) const {
|
|
BpTree bptree = this->make_empty_bptree();
|
|
/* 2nd tree, just to spoil memory locality */
|
|
BpTree bptree2 = this->make_empty_bptree();
|
|
|
|
for (std::uint32_t x : random_testdata.u1v()) {
|
|
bptree.insert(BpTree::value_type(x, 10 * x));
|
|
/* 2nd tree to interfere with locality */
|
|
for (std::uint32_t y = 0; y < 8; ++y) {
|
|
bptree2.insert(BpTree::value_type(8*x+y, 10 * (8*x+y)));
|
|
}
|
|
}
|
|
|
|
return bptree;
|
|
} /*make_random_bptree1*/
|
|
|
|
BpTree make_random_bptree12(RandomTestData const & random_testdata) const {
|
|
BpTree bptree = this->make_empty_bptree();
|
|
/* 2nd tree, just to spoil memory locality */
|
|
BpTree bptree2 = this->make_empty_bptree();
|
|
|
|
for (std::uint32_t x : random_testdata.u12_v()) {
|
|
bptree.insert(BpTree::value_type(x, 10 * x));
|
|
/* 2nd tree to interfere with locality */
|
|
for (std::uint32_t y = 0; y < 8; ++y) {
|
|
bptree2.insert(BpTree::value_type(8*x+y, 10 * (8*x+y)));
|
|
}
|
|
}
|
|
|
|
return bptree;
|
|
} /*make_random_bptree12*/
|
|
|
|
void run_unit_test(xo::rng::xoshiro256ss * p_rgen) const;
|
|
|
|
virtual void run_insert_benchmark(RandomTestData const & random_testdata) const override;
|
|
virtual void run_erase_benchmark(RandomTestData const & random_testdata) const override;
|
|
virtual void run_lookup_benchmark(RandomTestData const & random_testdata) const override;
|
|
virtual void run_traverse_benchmark(RandomTestData const & random_testdata) const override;
|
|
|
|
/* test (or benchmark) name -- 1st argument to catch2 TEST_CASE() / BENCHMARK() / SECTION() macro */
|
|
char const * test_name_ = nullptr;
|
|
/* exercise B+ tree with this branching factor */
|
|
std::size_t branching_factor_ = 0;
|
|
/* for benchmarks only: if true enable verbose logging of B+ tree operations. otherwise not used */
|
|
bool debug_flag_ = false;
|
|
}; /*BtreeTestParams*/
|
|
|
|
void
|
|
BtreeTestParams::run_unit_test(xo::rng::xoshiro256ss * p_rgen) const
|
|
{
|
|
std::size_t branching_factor = this->branching_factor_;
|
|
|
|
/* perform a series of tests with increasing scale */
|
|
for (std::uint32_t n = 0; n <= 1024;) {
|
|
if (n == 0) {
|
|
bool ok_flag = false;
|
|
|
|
for (std::uint32_t attention = 0; !ok_flag && (attention < 2); ++attention) {
|
|
ok_flag = true;
|
|
|
|
bool debug_flag = (attention == 1);
|
|
|
|
BtreeProperties properties(branching_factor,
|
|
debug_flag);
|
|
BpTree bptree(properties);
|
|
|
|
scope log(XO_DEBUG2(debug_flag, "bptree"),
|
|
xtag("vm_page_size", Machdep::get_page_size()),
|
|
xtag("branching_factor", bptree.branching_factor()),
|
|
xtag("leaf_node_size", sizeof(BpTree::LeafNodeType)),
|
|
xtag("internal_node_size", sizeof(BpTree::InternalNodeType)));
|
|
|
|
REQUIRE_ORCAPTURE(ok_flag, debug_flag, bptree.size() == 0);
|
|
REQUIRE_ORCAPTURE(ok_flag, debug_flag, bptree.verify_ok(true) == true);
|
|
|
|
log && log(xtag("size", n));
|
|
|
|
ok_flag &= TreeUtil<BpTree>::check_bidirectional_iterator(0 /*dvalue - not used*/,
|
|
debug_flag,
|
|
bptree);
|
|
|
|
ok_flag &= TreeUtil<BpTree>::test_clear(debug_flag, &bptree);
|
|
|
|
log.end_scope();
|
|
}
|
|
} else {
|
|
/* for each tree size, do multiple trials;
|
|
* choosing different pseudorandom key order for each trial
|
|
*/
|
|
for (std::uint32_t trial = 0; trial < 10; ++trial) {
|
|
/* repeated trials with different rng state */
|
|
|
|
bool ok_flag = false;
|
|
|
|
for (std::uint32_t attention = 0; !ok_flag && (attention < 2); ++attention) {
|
|
ok_flag = true;
|
|
|
|
/* attention=0:
|
|
* - no logging
|
|
* - detect assertion failures, but don't report them to catch
|
|
* attention=1:
|
|
* - only runs if failure detected with attention=0
|
|
* - full logging
|
|
* - report to catch
|
|
*/
|
|
|
|
bool debug_flag = (attention == 1);
|
|
|
|
BtreeProperties properties(branching_factor,
|
|
debug_flag);
|
|
BpTree bptree(properties);
|
|
|
|
scope log(XO_DEBUG2(debug_flag, "bptree"),
|
|
xtag("vm_page_size", Machdep::get_page_size()),
|
|
xtag("branching_factor", bptree.branching_factor()),
|
|
xtag("leaf_node_size", sizeof(BpTree::LeafNodeType)),
|
|
xtag("internal_node_size", sizeof(BpTree::InternalNodeType)));
|
|
|
|
REQUIRE_ORCAPTURE(ok_flag, debug_flag, bptree.size() == 0);
|
|
REQUIRE_ORCAPTURE(ok_flag, debug_flag, bptree.verify_ok(true) == true);
|
|
|
|
log && log(xtag("size", n), xtag("trial", trial));
|
|
|
|
/* insert [0..n-1] in random order */
|
|
ok_flag &= TreeUtil<BpTree>::random_inserts(n, debug_flag, p_rgen, &bptree);
|
|
|
|
/* verification problem -> print tree */
|
|
log && log(xtag("bptree", (char const *)"..."));
|
|
if (log) bptree.print(std::cout, log.nesting_level() + 2);
|
|
|
|
try {
|
|
REQUIRE_ORCAPTURE(ok_flag, debug_flag, bptree.verify_ok(debug_flag));
|
|
} catch(std::exception & ex) {
|
|
log && log(xtag("exception", ex.what()));
|
|
}
|
|
|
|
if (properties.ordinal_enabled()) {
|
|
ok_flag &= TreeUtil<BpTree>::check_ordinal_lookup(0 /*dvalue*/,
|
|
debug_flag,
|
|
bptree);
|
|
}
|
|
|
|
/* verify inorder traverse, using iterator api */
|
|
ok_flag &= TreeUtil<BpTree>::check_bidirectional_iterator(0,
|
|
debug_flag,
|
|
bptree);
|
|
|
|
ok_flag &= TreeUtil<BpTree>::random_lookups(debug_flag,
|
|
bptree,
|
|
p_rgen);
|
|
|
|
if (properties.ordinal_enabled()) {
|
|
/* paranoid check that iteration / random_lookups didn't somehow disturb tree */
|
|
ok_flag &= TreeUtil<BpTree>::check_ordinal_lookup(0 /*dvalue*/,
|
|
debug_flag,
|
|
bptree);
|
|
}
|
|
|
|
/* TODO:
|
|
* - check_reduced_sum()
|
|
* - check_ordinal_lookup()
|
|
* - check_bidirectional_iterator()
|
|
* - random_updates()
|
|
* - check_ordinal_lookup()
|
|
* - check_bidirectional_iterator()
|
|
* - check_reduced_sum()
|
|
*/
|
|
|
|
/* remove [0..n-1] in random order */
|
|
ok_flag &= TreeUtil<BpTree>::random_removes(debug_flag, p_rgen, &bptree);
|
|
|
|
/* insert [0..n-1] again, so we can test .clear() */
|
|
ok_flag &= TreeUtil<BpTree>::random_inserts(n, debug_flag, p_rgen, &bptree);
|
|
|
|
ok_flag &= TreeUtil<BpTree>::test_clear(debug_flag, &bptree);
|
|
|
|
log.end_scope();
|
|
} /*loop over attention value*/
|
|
} /*loop over trial#*/
|
|
}
|
|
|
|
if (n == 0)
|
|
n = 1;
|
|
else
|
|
n = 2*n;
|
|
}
|
|
} /*run_unit_test*/
|
|
|
|
void
|
|
BtreeTestParams::run_insert_benchmark(RandomTestData const & random_testdata) const
|
|
{
|
|
BENCHMARK_ADVANCED(this->test_name_)(Catch::Benchmark::Chronometer clock)
|
|
{
|
|
std::size_t n = random_testdata.u1v().size();
|
|
|
|
BpTree bptree = std::move(this->make_random_bptree1(random_testdata));
|
|
|
|
/* benchmark additional inserts (don't want to benchmark on empty tree) */
|
|
clock.measure([&](int seq) {
|
|
/* catch2 decides how many times to run this lambda,
|
|
* in effort to get statistically valid sample.
|
|
*
|
|
* If it calls lambda n times, then seq will increase from [0 .. n-1]
|
|
*/
|
|
|
|
std::size_t key = random_testdata.u2v()[seq % n];
|
|
double value = 10 * key;
|
|
|
|
bptree.insert(BpTree::value_type(key, value));
|
|
|
|
return bptree.size();
|
|
});
|
|
};
|
|
} /*run_insert_benchmark*/
|
|
|
|
void
|
|
BtreeTestParams::run_erase_benchmark(RandomTestData const & random_testdata) const
|
|
{
|
|
BENCHMARK_ADVANCED(this->test_name_)(Catch::Benchmark::Chronometer clock)
|
|
{
|
|
std::size_t n = random_testdata.u1v().size();
|
|
|
|
/* b+ tree with 2n elements */
|
|
BpTree bptree = std::move(this->make_random_bptree12(random_testdata));
|
|
|
|
/* measure time to remove n elements */
|
|
clock.measure([&](int seq) {
|
|
/* catch2 decides how many times to run this lambda,
|
|
* in effort to get statistically valid sample.
|
|
*
|
|
* If it calls lambda n times, then seq will increase from [0 .. n-1]
|
|
*/
|
|
|
|
//std::clog << "i=" << i << std::endl;
|
|
bptree.erase(random_testdata.u1v()[seq % n]);
|
|
|
|
return bptree.size();
|
|
});
|
|
};
|
|
} /*run_erase_benchmark*/
|
|
|
|
void
|
|
BtreeTestParams::run_lookup_benchmark(RandomTestData const & random_testdata) const
|
|
{
|
|
BENCHMARK_ADVANCED(this->test_name_)(Catch::Benchmark::Chronometer clock)
|
|
{
|
|
std::size_t n = random_testdata.u1v().size();
|
|
|
|
BpTree bptree = std::move(this->make_random_bptree1(random_testdata));
|
|
|
|
/* benchmark random lookups */
|
|
clock.measure([&](int seq) {
|
|
/* catch2 decides how many times to run this lambda,
|
|
* in effort to get statistically valid sample.
|
|
*
|
|
* If it calls lambda n times, then seq will increase from [0 .. n-1]
|
|
*/
|
|
|
|
std::size_t key = random_testdata.u1v()[seq % n];
|
|
|
|
double value = bptree[key];
|
|
|
|
return value;
|
|
});
|
|
};
|
|
} /*run_lookup_benchmark*/
|
|
|
|
void
|
|
BtreeTestParams::run_traverse_benchmark(RandomTestData const & random_testdata) const
|
|
{
|
|
BENCHMARK_ADVANCED(this->test_name_)(Catch::Benchmark::Chronometer clock)
|
|
{
|
|
std::size_t n = random_testdata.u1v().size();
|
|
|
|
BpTree bptree = std::move(this->make_random_bptree1(random_testdata));
|
|
|
|
/* benchmark traverse */
|
|
BpTree::const_iterator ix = bptree.begin();
|
|
|
|
clock.measure([&](int seq) {
|
|
/* catch2 decides how many times to run this lambda,
|
|
* in effort to get statistically valid sample.
|
|
*
|
|
* If it calls lambda n times, then seq will increase from [0 .. n-1]
|
|
*/
|
|
|
|
if (seq % n == 0)
|
|
ix = bptree.begin();
|
|
|
|
return ix++;
|
|
});
|
|
};
|
|
} /*run_traverse_benchmark*/
|
|
|
|
TEST_CASE("bptree", "[bplustree]") {
|
|
uint64_t seed = 14950349842636922572UL;
|
|
/* can reseed from /dev/random with: */
|
|
//Seed<xo::rng::xoshiro256ss> seed;
|
|
|
|
auto rgen = xo::rng::xoshiro256ss(seed);
|
|
|
|
/* exercise multiple branching factors */
|
|
std::array<BtreeTestParams, 4> const params_v
|
|
= {{
|
|
BtreeTestParams("bf=4",
|
|
4 /*branching_factor*/,
|
|
false /*debug_flag - not used*/),
|
|
BtreeTestParams("bf=12",
|
|
12 /*branching_factor*/,
|
|
false /*debug_flag - not used*/),
|
|
BtreeTestParams("bf=28",
|
|
28 /*branching_factor*/,
|
|
false /*debug_flag - not used*/),
|
|
BtreeTestParams("bf=60",
|
|
60 /*branching_factor*/,
|
|
false /*debug_flag - not used*/)
|
|
}};
|
|
|
|
for (std::uint32_t i_pm = 0; i_pm < params_v.size(); ++i_pm) {
|
|
SECTION(params_v[i_pm].test_name_) {
|
|
params_v[i_pm].run_unit_test(&rgen);
|
|
}
|
|
}
|
|
} /*TEST_CASE(bptree)*/
|
|
|
|
/* to run:
|
|
* $ ./utest.tree [!benchmark]
|
|
*
|
|
* looks like ospage4 (1k nodes) gets best performance
|
|
*/
|
|
TEST_CASE("bptree-benchmark", "[!benchmark]") {
|
|
using BtreeProperties = BplusStdProperties<BtreeKey, BtreeValue>;
|
|
|
|
/* 2 cache lines per node (though note that we're not aligning nodes on cacheline boundaries) */
|
|
std::size_t const c_cacheline_branching_factor = 4; // BtreeProperties::default_cacheline_branching_factor();
|
|
std::size_t const c_ospage16_branching_factor = BtreeProperties::branching_factor_for_size(Machdep::get_page_size() / 16);
|
|
std::size_t const c_ospage8_branching_factor = BtreeProperties::branching_factor_for_size(Machdep::get_page_size() / 8);
|
|
std::size_t const c_ospage4_branching_factor = BtreeProperties::branching_factor_for_size(Machdep::get_page_size() / 4);
|
|
std::size_t const c_ospage2_branching_factor = BtreeProperties::branching_factor_for_size(Machdep::get_page_size() / 2);
|
|
std::size_t const c_ospage1_branching_factor = BtreeProperties::branching_factor_for_size(Machdep::get_page_size());
|
|
|
|
/* random seed -- we don't need deterministic behavior for benchmarking, unless we encounter internal logic error */
|
|
//std::uint64_t seed = 17372468046414980217UL;
|
|
Seed<xo::rng::xoshiro256ss> seed;
|
|
|
|
auto rgen = xo::rng::xoshiro256ss(seed);
|
|
|
|
constexpr bool c_debug_flag = false;
|
|
|
|
/* n keys [0 .. n-1] */
|
|
std::uint32_t n = 25000;
|
|
|
|
RandomTestData random_testdata(n, &rgen);
|
|
|
|
#ifdef OBSOLETE
|
|
/* random permutation of [0..n-1] */
|
|
std::vector<std::uint32_t> u(n);
|
|
{
|
|
for (std::uint32_t i=0; i<n; ++i)
|
|
u[i] = i;
|
|
std::shuffle(u.begin(), u.end(), rgen);
|
|
}
|
|
|
|
/* random permutation of [n..2n-1] */
|
|
std::vector<std::uint32_t> u2(n);
|
|
{
|
|
for (std::uint32_t i=0; i<n; ++i)
|
|
u2[i] = n+i;
|
|
std::shuffle(u2.begin(), u2.end(), rgen);
|
|
}
|
|
#endif
|
|
|
|
std::clog << "rng-seed=" << seed << "\n"
|
|
<< "opage16-branching-factor=" << c_ospage16_branching_factor << "\n"
|
|
<< "opage16-leaf-size=" << sizeof(BpTree::LeafNodeType) + c_ospage16_branching_factor * sizeof(BpTree::LeafNodeItemType) << "\n"
|
|
<< "opage16-internal-size=" << sizeof(BpTree::InternalNodeType) + c_ospage16_branching_factor * sizeof(BpTree::InternalNodeItemType) << "\n"
|
|
<< "opage8-branching-factor=" << c_ospage8_branching_factor << "\n"
|
|
<< "opage8-leaf-size=" << sizeof(BpTree::LeafNodeType) + c_ospage8_branching_factor * sizeof(BpTree::LeafNodeItemType) << "\n"
|
|
<< "opage8-internal-size=" << sizeof(BpTree::InternalNodeType) + c_ospage8_branching_factor * sizeof(BpTree::InternalNodeItemType) << "\n"
|
|
<< "opage4-branching-factor=" << c_ospage4_branching_factor << "\n"
|
|
<< "opage4-leaf-size=" << sizeof(BpTree::LeafNodeType) + c_ospage4_branching_factor * sizeof(BpTree::LeafNodeItemType) << "\n"
|
|
<< "opage4-internal-size=" << sizeof(BpTree::InternalNodeType) + c_ospage4_branching_factor * sizeof(BpTree::InternalNodeItemType) << "\n"
|
|
<< "opage2-branching-factor=" << c_ospage2_branching_factor << "\n"
|
|
<< "opage2-leaf-size=" << sizeof(BpTree::LeafNodeType) + c_ospage2_branching_factor * sizeof(BpTree::LeafNodeItemType) << "\n"
|
|
<< "opage2-internal-size=" << sizeof(BpTree::InternalNodeType) + c_ospage2_branching_factor * sizeof(BpTree::InternalNodeItemType) << "\n"
|
|
<< "opage1-branching-factor=" << c_ospage1_branching_factor << "\n"
|
|
<< "default-branching-factor[cacheline]=" << BplusStdProperties<BtreeKey, BtreeValue>::default_cacheline_branching_factor() << "\n"
|
|
<< "page-size=" << Machdep::get_page_size() << "\n"
|
|
<< "cache-line-size=" << Machdep::get_cache_line_size() << "\n"
|
|
<< "generic-node-overhead=" << sizeof(BpTree::GenericNodeType) << "\n"
|
|
<< "leaf-node-overhead=" << sizeof(BpTree::LeafNodeType) << "\n"
|
|
<< "leaf-node-item-size=" << sizeof(BpTree::LeafNodeItemType) << "\n"
|
|
<< "internal-node-overhead=" << sizeof(BpTree::InternalNodeType) << "\n"
|
|
<< "internal-node-item-size=" << sizeof(BpTree::InternalNodeItemType) << "\n"
|
|
<< "actual-cacheline-leaf-node-size=" << sizeof(BpTree::LeafNodeType) + c_cacheline_branching_factor * sizeof(BpTree::LeafNodeItemType) << "\n"
|
|
<< "actual-cacheline-internal-node-size=" << sizeof(BpTree::InternalNodeType) + c_cacheline_branching_factor * sizeof(BpTree::InternalNodeItemType) << "\n"
|
|
<< "actual-ospage-leaf-node-size=" << sizeof(BpTree::LeafNodeType) + c_ospage1_branching_factor * sizeof(BpTree::LeafNodeItemType) << "\n"
|
|
<< "actual-ospage-internal-node-size=" << sizeof(BpTree::InternalNodeType) + c_ospage1_branching_factor * sizeof(BpTree::InternalNodeItemType) << "\n"
|
|
<< "n=" << n << "\n"
|
|
<< std::endl;
|
|
|
|
{
|
|
std::array<std::unique_ptr<AbstractTestParams>, 7> const params_v
|
|
= {{
|
|
std::unique_ptr<AbstractTestParams>(new StdMapTestParams("std-map-insert")),
|
|
std::unique_ptr<AbstractTestParams>(new BtreeTestParams("bplus-min-insert",
|
|
c_cacheline_branching_factor,
|
|
false)),
|
|
std::unique_ptr<AbstractTestParams>(new BtreeTestParams("bplus-ospage16-insert",
|
|
c_ospage16_branching_factor,
|
|
false)),
|
|
std::unique_ptr<AbstractTestParams>(new BtreeTestParams("bplus-ospage8-insert",
|
|
c_ospage8_branching_factor,
|
|
false)),
|
|
std::unique_ptr<AbstractTestParams>(new BtreeTestParams("bplus-ospage4-insert",
|
|
c_ospage4_branching_factor,
|
|
false)),
|
|
std::unique_ptr<AbstractTestParams>(new BtreeTestParams("bplus-ospage2-insert",
|
|
c_ospage2_branching_factor,
|
|
false)),
|
|
std::unique_ptr<AbstractTestParams>(new BtreeTestParams("bplus-ospage-insert",
|
|
c_ospage1_branching_factor,
|
|
false))
|
|
}};
|
|
|
|
/* note: w/cacheline:
|
|
* getting 593ms for 10^6 inserts;
|
|
* i.e. ~593ns each
|
|
* w/ospage:
|
|
* getting 188ms for 10^6 inserts;
|
|
* i.e. ~188ns each
|
|
* (with ospage size 4k -> branching factor 252)
|
|
*/
|
|
for(std::uint32_t i_bm = 0; i_bm < params_v.size(); ++i_bm) {
|
|
params_v[i_bm]->run_insert_benchmark(random_testdata);
|
|
}
|
|
}
|
|
|
|
{
|
|
std::array<std::unique_ptr<AbstractTestParams>, 7> const params_v
|
|
= {{
|
|
std::unique_ptr<AbstractTestParams>(new StdMapTestParams("std-map-erase")),
|
|
std::unique_ptr<AbstractTestParams>(new BtreeTestParams("bplus-min-remove",
|
|
c_cacheline_branching_factor,
|
|
false)),
|
|
std::unique_ptr<AbstractTestParams>(new BtreeTestParams("bplus-ospage16-remove",
|
|
c_ospage16_branching_factor,
|
|
false)),
|
|
std::unique_ptr<AbstractTestParams>(new BtreeTestParams("bplus-ospage8-remove",
|
|
c_ospage8_branching_factor,
|
|
false)),
|
|
std::unique_ptr<AbstractTestParams>(new BtreeTestParams("bplus-ospage4-remove",
|
|
c_ospage8_branching_factor,
|
|
false)),
|
|
std::unique_ptr<AbstractTestParams>(new BtreeTestParams("bplus-ospage2-remove",
|
|
c_ospage8_branching_factor,
|
|
false)),
|
|
std::unique_ptr<AbstractTestParams>(new BtreeTestParams("bplus-ospage1-remove",
|
|
c_ospage1_branching_factor,
|
|
false))
|
|
}};
|
|
|
|
/* note: cacheline: getting 72us for 10^2 removes;
|
|
* i.e. ~7.2ns each
|
|
*
|
|
* ospage: getting 243us for 10^4 removes;
|
|
* i.e. ~24ns each
|
|
*/
|
|
for (std::uint32_t i_bm = 0; i_bm < params_v.size(); ++i_bm) {
|
|
params_v[i_bm]->run_erase_benchmark(random_testdata);
|
|
}
|
|
}
|
|
|
|
{
|
|
std::array<std::unique_ptr<AbstractTestParams>, 7> const params_v
|
|
= {{
|
|
std::unique_ptr<AbstractTestParams>(new StdMapTestParams("std-map-lookup")),
|
|
std::unique_ptr<AbstractTestParams>(new BtreeTestParams("bplus-min-lookup",
|
|
c_cacheline_branching_factor,
|
|
false)),
|
|
std::unique_ptr<AbstractTestParams>(new BtreeTestParams("bplus-ospage16-lookup",
|
|
c_ospage16_branching_factor,
|
|
false)),
|
|
std::unique_ptr<AbstractTestParams>(new BtreeTestParams("bplus-ospage8-lookup",
|
|
c_ospage8_branching_factor,
|
|
false)),
|
|
std::unique_ptr<AbstractTestParams>(new BtreeTestParams("bplus-ospage4-lookup",
|
|
c_ospage4_branching_factor,
|
|
false)),
|
|
std::unique_ptr<AbstractTestParams>(new BtreeTestParams("bplus-ospage2-lookup",
|
|
c_ospage2_branching_factor,
|
|
false)),
|
|
std::unique_ptr<AbstractTestParams>(new BtreeTestParams("bplus-ospage1-lookup",
|
|
c_ospage1_branching_factor,
|
|
false))
|
|
}};
|
|
|
|
/* note: cacheline:
|
|
* getting 850us for 10^4 lookups;
|
|
* -> ~85ns each
|
|
* ospage:
|
|
* getting 585us for 10^4 lookups;
|
|
* -> ~58ns each
|
|
*/
|
|
for (std::uint32_t i_bm = 0; i_bm < params_v.size(); ++i_bm) {
|
|
params_v[i_bm]->run_lookup_benchmark(random_testdata);
|
|
}
|
|
}
|
|
|
|
{
|
|
std::array<std::unique_ptr<AbstractTestParams>, 7> const params_v
|
|
= {{
|
|
std::unique_ptr<AbstractTestParams>(new StdMapTestParams("std-map-traverse")),
|
|
std::unique_ptr<AbstractTestParams>(new BtreeTestParams("bplus-min-traverse",
|
|
c_cacheline_branching_factor,
|
|
false)),
|
|
std::unique_ptr<AbstractTestParams>(new BtreeTestParams("bplus-ospage16-traverse",
|
|
c_ospage16_branching_factor,
|
|
false)),
|
|
std::unique_ptr<AbstractTestParams>(new BtreeTestParams("bplus-ospage8-traverse",
|
|
c_ospage8_branching_factor,
|
|
false)),
|
|
std::unique_ptr<AbstractTestParams>(new BtreeTestParams("bplus-ospage4-traverse",
|
|
c_ospage4_branching_factor,
|
|
false)),
|
|
std::unique_ptr<AbstractTestParams>(new BtreeTestParams("bplus-ospage2-traverse",
|
|
c_ospage2_branching_factor,
|
|
false)),
|
|
std::unique_ptr<AbstractTestParams>(new BtreeTestParams("bplus-ospage1-traverse",
|
|
c_ospage1_branching_factor,
|
|
false))
|
|
}};
|
|
|
|
/* note: cacheline: getting 25us to traverse tree of size 10^4
|
|
* -> ~2.5ns each
|
|
* note: ospage: getting 6us to traverse tree of size 10^4
|
|
* -> ~0.6ns each
|
|
*/
|
|
for (std::uint32_t i_bm = 0; i_bm < params_v.size(); ++i_bm) {
|
|
params_v[i_bm]->run_traverse_benchmark(random_testdata);
|
|
}
|
|
}
|
|
|
|
} /*TEST_CASE(bptree-benchmark)*/
|
|
} /*namespace*/
|
|
|
|
/* end bplustree.cpp */
|