Add 'xo-ordinaltree/' from commit 'e40dc2daab'

git-subtree-dir: xo-ordinaltree
git-subtree-mainline: aaa3864229
git-subtree-split: e40dc2daab
This commit is contained in:
Roland Conybeare 2025-05-10 18:59:48 -05:00
commit 9778da2094
21 changed files with 9075 additions and 0 deletions

View file

@ -0,0 +1,122 @@
name: build xo-ordinaltree + xo dependencies
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
env:
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
BUILD_TYPE: Release
jobs:
build:
# The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac.
# You can convert this to a matrix build if you need cross-platform coverage.
# See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix
runs-on: ubuntu-latest
steps:
- name: checkout source
uses: actions/checkout@v3
- name: Install catch2
# install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]]
run: sudo apt-get install -y catch2
- name: Install libbsd-dev
# provides arc4random_buf in randomgen
run: sudo apt-get install -y libbsd-dev
# ----------------------------------------------------------------
- name: Clone xo-cmake
uses: actions/checkout@v3
with:
repository: Rconybea/xo-cmake
path: repo/xo-cmake
- name: Configure xo-cmake
run: cmake -B ${{github.workspace}}/build_xo-cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/xo-cmake
- name: Build xo-cmake (trivial)
run: cmake --build ${{github.workspace}}/build_xo-cmake --config ${{env.BUILD_TYPE}}
- name: Install xo-cmake
run: cmake --install ${{github.workspace}}/build_xo-cmake
# ----------------------------------------------------------------
- name: Clone indentlog
uses: actions/checkout@v3
with:
repository: Rconybea/indentlog
path: repo/indentlog
- name: Configure indentlog
# configure cmake for indentlog in dedicated build directory.
run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/indentlog
- name: Build indentlog
run: cmake --build ${{github.workspace}}/build_indentlog --config ${{env.BUILD_TYPE}}
- name: Install indentlog
# install into ${{github.workspace}}/local
run: cmake --install ${{github.workspace}}/build_indentlog
# ----------------------------------------------------------------
- name: Clone randomgen
uses: actions/checkout@v3
with:
repository: Rconybea/randomgen
path: repo/randomgen
- name: Configure randomgen
# configure cmake for randomgen in dedicated build directory.
run: cmake -B ${{github.workspace}}/build_randomgen -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/randomgen
- name: Build randomgen
run: cmake --build ${{github.workspace}}/build_randomgen --config ${{env.BUILD_TYPE}}
- name: Install randomgen
# install into ${{github.workspace}}/local
run: cmake --install ${{github.workspace}}/build_randomgen
# ----------------------------------------------------------------
- name: Clone refcnt
uses: actions/checkout@v3
with:
repository: Rconybea/refcnt
path: repo/refcnt
- name: Configure refcnt
# configure cmake for refcnt in dedicated build directory.
run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/refcnt
- name: Build refcnt
run: cmake --build ${{github.workspace}}/build_refcnt --config ${{env.BUILD_TYPE}}
- name: Install refcnt
# install into ${{github.workspace}}/local
run: cmake --install ${{github.workspace}}/build_refcnt
# ----------------------------------------------------------------
- name: Configure self (xo-ordinaltree)
# Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.
# See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type
run: cmake -B ${{github.workspace}}/build_ordinaltree -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}}
- name: Build self (xo-ordinaltree)
# Build your program with the given configuration
run: cmake --build ${{github.workspace}}/build_ordinaltree --config ${{env.BUILD_TYPE}}
- name: Test self (xo-ordinaltree)
working-directory: ${{github.workspace}}/build_ordinaltree
# Execute tests defined by the CMake configuration.
# See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail
run: ctest -C ${{env.BUILD_TYPE}}

6
xo-ordinaltree/.gitignore vendored Normal file
View file

@ -0,0 +1,6 @@
# clangd working space (see emacs+lsp)
.cache
# typical cmake build directory (source-tree-nephew)
.build*
# symlink to builddir/compile_commands.json; should be set manually in dev sandbox
compile_commands.json

View file

@ -0,0 +1,43 @@
# xo-ordinaltree/CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(xo_ordinaltree VERSION 0.1)
enable_language(CXX)
# common XO macros (see github:Rconybea/xo-cmake)
include(GNUInstallDirs)
include(cmake/xo-bootstrap-macros.cmake)
xo_cxx_toplevel_options3()
# ----------------------------------------------------------------
# c++ settings
set(PROJECT_CXX_FLAGS "")
add_definitions(${PROJECT_CXX_FLAGS})
# ----------------------------------------------------------------
# output targets
add_subdirectory(utest)
# ----------------------------------------------------------------
# header-only library
set(SELF_LIB xo_ordinaltree)
xo_add_headeronly_library(${SELF_LIB})
# ----------------------------------------------------------------
#
xo_install_library4(${SELF_LIB} ${PROJECT_NAME}Targets)
# (note: ..Targets from xo_install_library2())
xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets)
# ----------------------------------------------------------------
# input dependencies
# NOTE: dependency set here must be kept consistent with ordinaltree/cmake/xo_ordinaltreeConfig.cmake.in
# xo-ordinaltree is also header-only
xo_headeronly_dependency(${SELF_LIB} randomgen)

33
xo-ordinaltree/README.md Normal file
View file

@ -0,0 +1,33 @@
# ordinal tree library
## Getting Started
### build + install dependencies
- see [github/Rconybea/randomgen](https://github.com/Rconybea/randomgen) -- random number generators e.g. xoshiro256ss
- see [github/Rconybea/refcnt](https://github.com/Rconybea/refcnt) -- intrusive reference-counting
### build + install
```
$ cd xo-ordinaltree
$ mkdir build
$ cd build
$ INSTALL_PREFIX=/usr/local # or wherever you prefer
$ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} ..
$ make
$ make install
```
### build for unit test coverage
```
$ cd xo-ordinaltree
$ mkdir build-ccov
$ cd build-ccov
$ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCODE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug ..
```
### LSP support
```
$ cd xo-ordinaltree
$ ln -s build/compile_commands.json # lsp will look for compile_commands.json in the root of the source tree
```

View file

@ -0,0 +1,14 @@
if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix"))
# default to typical install location for xo-project-macros
set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake)
endif()
if (NOT XO_SUBMODULE_BUILD)
message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}")
message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}")
endif()
# needs to have been installed somewhere on CMAKE_MODULE_PATH,
# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX)
#
include(xo_macros/xo-project-macros)

View file

@ -0,0 +1,6 @@
@PACKAGE_INIT@
include(CMakeFindDependencyMacro)
find_dependency(randomgen)
include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake")
check_required_components("@PROJECT_NAME@")

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,280 @@
/* @file BplusTreeNode.hpp */
#pragma once
#include "IteratorUtil.hpp"
#include "bplustree_tags.hpp"
#include "xo/indentlog/scope.hpp"
#include "xo/indentlog/print/tag.hpp"
#include <memory> // for std::unique_ptr
#include <string>
namespace xo {
namespace tree {
/* forward decl (see GenericNode.hpp) */
template <typename Key, typename Value, typename Properties>
class GenericNode;
/* forward decl (see InternalNode.hpp) */
template <typename Key, typename Value, typename Properties>
class InternalNode;
namespace detail {
/* forward decl (see Iterator.hpp) */
template <typename Key, typename Value, typename Properties>
class ConstIterator;
}
// ----- NodeType -----
enum class NodeType { internal, leaf };
inline std::string node_type2str(NodeType x) {
switch(x) {
case NodeType::internal: return "internal";
case NodeType::leaf: return "leaf";
}
return "???";
} /*node_type2str*/
inline std::ostream & operator<<(std::ostream & os, NodeType x) {
os << node_type2str(x);
return os;
} /*operator<<*/
/* see bplustree/LeafNode.hpp */
template <typename Key, typename Value, typename Properties>
struct LeafNode;
/* see bplustree/InternalNode.hpp */
template <typename Key, typename Value, typename Properties>
struct InternalNode;
// ----- NodeItem + NodeItemPlaceholder -----
template <NodeType NType, typename Key, typename Value, typename Properties>
struct NodeItem {};
/* struct with same size as NodeItem<NType, Key, Value, Properties>, but POD + with trivial ctor/dtor */
template <NodeType NType, typename Key, typename Value, typename Properties>
struct NodeItemPlaceholder {
std::uint8_t mem_v_[sizeof(NodeItem<NType, Key, Value, Properties>)];
}; /*NodeItemPlaceholder*/
// ----- FindResult -----
/* report a node, along with its location (0-based index) within parent.
* use nullptr for .node if item/node not found
* use 0 for .ix if node is root (i.e. has no parent)
*
* expect ConcreteNodeType = LeafNode<..> | InternalNode<..>
*/
template <typename ConcreteNodeType>
struct FindNodeResult {
public:
FindNodeResult() = default;
FindNodeResult(FindNodeResult const & x) = default;
FindNodeResult(std::size_t ix, ConcreteNodeType * node) : ix_{ix}, node_{node} {}
std::size_t ix() const { return ix_; }
ConcreteNodeType * node() const { return node_; }
private:
/* 0-based index within parent */
std::size_t ix_ = 0;
/* a B+ tree node */
ConcreteNodeType * node_ = nullptr;
}; /*FindNodeResult*/
template <typename Key,
typename Value,
typename Properties, tags::ordinal_tag OrdinalTag = Properties::ordinal_tag_value()>
struct BplusTreeUtil {
public:
using GenericNodeType = GenericNode<Key, Value, Properties>;
using InternalNodeType = InternalNode<Key, Value, Properties>;
using LeafNodeType = LeafNode<Key, Value, Properties>;
using const_iterator = detail::ConstIterator<Key, Value, Properties>;
static std::size_t get_node_size(GenericNodeType const * node) {
return node->size();
}
/* only implemented for OrdinalTag = ordinal_enabled */
static void print_node_size(std::ostream & os, GenericNodeType const * node) {
using xo::xtag;
os << (node ? node->size() : 0UL);
}
static const_iterator find_ith(GenericNodeType * generic_node,
std::size_t i_tree,
const_iterator cend) {
using xo::xtag;
if (!generic_node)
return cend;
std::size_t iter = 0;
/* 100-level B+ tree won't fit in memory -- would have at least 2^100 nodes! */
while (iter < 100) {
switch (generic_node->node_type()) {
case NodeType::leaf:
return const_iterator(detail::ID_Forward /*dirn*/,
detail::IL_Regular /*loc*/,
reinterpret_cast<LeafNodeType const *>(generic_node),
i_tree /*item_ix*/);
case NodeType::internal:
{
/* scan for ith member (counting from 0) */
InternalNodeType const * internal_node
= reinterpret_cast<InternalNodeType const *>(generic_node);
std::size_t sum_z = 0;
std::size_t z = 0;
std::size_t i = 0;
std::size_t n = internal_node->n_elt();
for (; i<n; ++i) {
GenericNodeType * child_node = internal_node->lookup_elt(i).child();
z = child_node->size();
if (i_tree < sum_z + z) {
/* continue search in i'th child of internal_node;
* accounting for the sum_z members in nodes to the left of i_child
*/
generic_node = child_node;
i_tree = i_tree - sum_z;
break;
}
sum_z += z;
}
if (i == n) {
throw std::runtime_error(tostr("BplusTree::find_ith: internal index failure",
xtag("i_tree", i_tree),
xtag("last_z", z),
xtag("n", internal_node->n_elt()),
xtag("sum_z", sum_z)));
}
}
break;
} /*switch*/
++iter;
} /*loop over descending internal node path*/
throw std::runtime_error(tostr("BplusTree::find_ith: internal loop failure",
xtag("iter", iter)));
/* impossible! */
return cend;
} /*find_ith*/
static void node_clear_size(InternalNodeType * node) {
node->clear_size();
}
static void node_add_size(InternalNodeType * node, std::size_t incr_z) {
node->add_size(incr_z);
}
static void node_sub_size(InternalNodeType * node, std::size_t decr_z) {
node->sub_size(decr_z);
}
static void post_modify_add_ancestor_size(InternalNodeType * node, std::size_t incr_z, bool debug_flag) {
using xo::scope;
using xo::xtag;
scope log(XO_DEBUG(debug_flag));
while (node) {
log && log(xtag("node", node),
xtag("old_z", node->size()),
xtag("incr_z", incr_z));
node->add_size(incr_z);
node = node->parent();
}
} /*post_modify_add_ancestor_size*/
static void post_modify_sub_ancestor_size(InternalNodeType * node, std::size_t decr_z, bool debug_flag) {
using xo::scope;
using xo::xtag;
scope log(XO_DEBUG(debug_flag));
while (node) {
log && log(xtag("node", node),
xtag("old_z", node->size()),
xtag("decr_z", decr_z));
node->sub_size(decr_z);
node = node->parent();
}
} /*post_modify_sub_ancestor_size*/
};
template <typename Key,
typename Value,
typename Properties>
struct BplusTreeUtil<Key, Value, Properties, tags::ordinal_disabled> {
public:
using GenericNodeType = GenericNode<Key, Value, Properties>;
using InternalNodeType = InternalNode<Key, Value, Properties>;
using LeafNodeType = LeafNode<Key, Value, Properties>;
using const_iterator = detail::ConstIterator<Key, Value, Properties>;
static std::size_t get_node_size(GenericNodeType const * node) { return 0; }
static void print_node_size(std::ostream & os, GenericNodeType const * node) {
os << "n/a";
}
/* find_ith not implemented without ordinal feature */
static const_iterator find_ith(GenericNodeType * generic_node,
std::size_t i_tree,
const_iterator cend) {
throw std::runtime_error("BplusTreeUtil::find_ith: not implemented (requires tags::ordinal_enabled)");
}
/* per-node size not implemented, so these are no-ops */
static void node_clear_size(InternalNodeType * node) {}
static void node_add_size(InternalNodeType * node, std::size_t incr_z) {}
static void node_sub_size(InternalNodeType * node, std::size_t decr_z) {}
static void post_modify_add_ancestor_size(InternalNodeType * node, std::size_t incr_z, bool debug_flag) {}
static void post_modify_sub_ancestor_size(InternalNodeType * node, std::size_t decr_z, bool debug_flag) {}
};
} /*namespace tree*/
} /*namespace xo*/
namespace logutil {
template <typename Node>
struct nodesize {
explicit nodesize(Node const * x) : node_{x} {}
Node const * node() const { return node_; }
private:
Node const * node_ = nullptr;
}; /*nodesize*/
template <typename Node>
inline std::ostream & operator<<(std::ostream & os,
nodesize<Node> const & x) {
xo::tree::BplusTreeUtil<typename Node::PropertiesType::KeyType,
typename Node::PropertiesType::ValueType,
typename Node::PropertiesType>::print_node_size(os, x.node());
return os;
};
} /*namespace logutil*/
/* end BplusTreeUtil.hpp */

View file

@ -0,0 +1,123 @@
/* @file GenericNode.hpp */
#pragma once
#include "BplusTreeUtil.hpp"
#include "bplustree_tags.hpp"
#include <memory> // for std::unique_ptr
#include <string>
namespace xo {
namespace tree {
/* shim so we can partially specialize */
template <typename Properties, tags::ordinal_tag OrdinalTag = Properties::ordinal_tag_value()>
struct GenericNodeBase {
}; /*GenericNodeBase*/
template <typename Properties>
struct GenericNodeBase<Properties, tags::ordinal_enabled> {
/* #of items (key-value pairs) in this subtree */
virtual std::size_t size() const = 0;
}; /*GenericNodeShim*/
// ----- GenericNode -----
//
// base class for LeafNode, InternalNode
template <typename Key, typename Value, typename Properties>
class GenericNode : public GenericNodeBase<Properties> {
public:
using PropertiesType = Properties;
using InternalNodeType = InternalNode<Key, Value, Properties>;
using LeafNodeType = LeafNode<Key, Value, Properties>;
public:
explicit GenericNode(NodeType ntype, std::size_t branching_factor)
: node_type_{ntype}, branching_factor_{branching_factor} {}
virtual ~GenericNode() = default;
NodeType node_type() const { return node_type_; }
InternalNodeType * parent() const { return parent_; }
std::size_t n_elt() const { return n_elt_; }
std::size_t branching_factor() const { return branching_factor_; }
void set_parent(InternalNodeType * x) { this->parent_ = x; }
#ifdef OBSOLETE
/* #of items (key-value pairs) in this subtree */
virtual std::size_t size() const = 0;
#endif
virtual Key const & glb_key() const = 0;
/* support methods for BplusTree::verify()
* with_lub. true to use lub_key; false to ignore
* lub_key. if with_lub=true, strict least upper bound key (in B+ tree) for this subtree;
* all keys in this subtree must be strictly less than lub_key.
* ignored when with_lub=false
* lh_leaf. if null, this subtree contains the smallest key in ancestor B+ tree;
* if non-null, lh_leaf's rightmost key is immediate predecessor
* of leftmost key in this subtree
* rh_leaf. if null, this subtree contains the largest key in ancestor B+ tree;
* if non-null, rh_leaf's leftmost key is immediate successor
* of rightmost key in this subtree
*/
virtual std::size_t verify_helper(InternalNodeType const * parent,
bool with_lub,
Key const & lub_key,
LeafNodeType const * lh_leaf,
LeafNodeType const * rh_leaf) const = 0;
virtual void verify_glb_key(Key const & key) const = 0;
FindNodeResult<LeafNodeType const> c_find_min_leaf_node() const;
FindNodeResult<LeafNodeType const> c_find_max_leaf_node() const;
virtual FindNodeResult<LeafNodeType> find_min_leaf_node() = 0;
virtual FindNodeResult<LeafNodeType> find_max_leaf_node() = 0;
/* notification just before permanently removing this node from B+ tree */
virtual void notify_remove() {}
private:
/* NodeType::internal | NodeType::leaf */
NodeType node_type_;
/* pointer to parent node
* invariant: parent has direct pointer to this node,
* except briefly during construction
*/
InternalNodeType * parent_ = nullptr;
protected:
/* #of non-empty elements (children) of this node
*
* invariant:
* - .elt_v[i].child.ptr is non-null for 0 <= i < .n_elt
* - for (0 < i < .n_elt):
* .elt_v[i-1].key < .elt_v[i].key
* - elt_v[i].key not defined for (i >= .n_elt)
*/
std::size_t n_elt_ = 0;
/* need to store actual branching factor, for LeafNode/InternalNode dtors */
std::size_t branching_factor_ = 0;
}; /*GenericNode*/
/* const version (non-const version below) */
template <typename Key, typename Value, typename Properties>
FindNodeResult<LeafNode<Key, Value, Properties> const>
GenericNode<Key, Value, Properties>::c_find_min_leaf_node() const {
InternalNode<Key, Value, Properties> * self = const_cast<InternalNode<Key, Value, Properties> *>(this);
return self->find_min_leaf_node();
} /*c_find_min_leaf_node*/
/* const version (non-const version below) */
template <typename Key, typename Value, typename Properties>
FindNodeResult<LeafNode<Key, Value, Properties> const>
GenericNode<Key, Value, Properties>::c_find_max_leaf_node() const {
InternalNode<Key, Value, Properties> * self = const_cast<InternalNode<Key, Value, Properties> *>(this);
return self->find_max_leaf_node();
} /*c_find_max_leaf_node*/
} /*namespace tree*/
} /*namespace xo*/
/* end GenericNode.hpp */

View file

@ -0,0 +1,768 @@
/* @file InternalNode.hpp */
#pragma once
#include "GenericNode.hpp"
#include "xo/indentlog/scope.hpp"
#include "xo/indentlog/print/tostr.hpp"
#include <cassert>
namespace xo {
namespace tree {
// ----- InternalNodeItem ------
/* see also: NodeItem<NodeType::leaf, Key, Value, Properties> */
template <typename Key, typename Value, typename Properties>
struct NodeItem<NodeType::internal, Key, Value, Properties> {
using GenericNodeType = GenericNode<Key, Value, Properties>;
public:
NodeItem() = default;
explicit NodeItem(std::unique_ptr<GenericNodeType> child)
: child_{std::move(child)} {
if (child_)
this->key_ = child_->glb_key();
}
Key const & key() const { return key_; }
GenericNodeType * child() const { return child_.get(); }
std::unique_ptr<GenericNodeType> release_child() { return std::move(child_); }
void set_key(Key key) { key_ = std::move(key); }
void notify_remove() {
if (child_)
child_->notify_remove();
} /*notify_remove*/
private:
/* invariant: .key is leftmost key in subtree rooted at .child
* (i.e. greatest lower bound for keys in that subtree)
*/
Key key_;
/* subtree. subtree has minimum key value .key */
std::unique_ptr<GenericNodeType> child_;
}; /*NodeItem */
template <typename Key, typename Value, typename Properties>
using InternalNodeItem = NodeItem<NodeType::internal, Key, Value, Properties>;
/* struct with same size as InternalNodeItem<Key,Properties>, but POD + with no ctor/dtor */
template <typename Key, typename Value, typename Properties>
using InternalNodeItemPlaceholder = NodeItemPlaceholder<NodeType::internal, Key, Value, Properties>;
/* default implements tags::ordinal_disabled; see partial specialization below for ordinal_enabled */
template <typename Key, typename Value, typename Properties, tags::ordinal_tag OrdinalTag = Properties::ordinal_tag_value()>
struct InternalNodeShim : public GenericNode<Key, Value, Properties> {
public:
using GenericNodeType = GenericNode<Key, Value, Properties>;
public:
InternalNodeShim(NodeType ntype, std::size_t branching_factor) : GenericNode<Key, Value, Properties>{ntype, branching_factor} {}
protected:
/* not implemented with tags::ordinal_disabled */
void assign_size(std::size_t z) {}
};
template <typename Key, typename Value, typename Properties>
struct InternalNodeShim<Key, Value, Properties, tags::ordinal_enabled> : public GenericNode<Key, Value, Properties> {
public:
using GenericNodeType = GenericNode<Key, Value, Properties>;
public:
InternalNodeShim(NodeType ntype, std::size_t branching_factor) : GenericNode<Key, Value, Properties>{ntype, branching_factor} {}
void clear_size() { this->size_ = 0; }
void add_size(std::size_t z) { this->size_ += z; }
void sub_size(std::size_t z) { this->size_ -= z; }
virtual std::size_t size() const override { return size_; }
protected:
void assign_size(std::size_t z) { this->size_ = z; }
protected:
std::size_t size_ = 0;
}; /*InternalNodeShim*/
/* require:
* - Properties.branching_factor()
*/
template <typename Key, typename Value, typename Properties>
struct InternalNode : public InternalNodeShim<Key, Value, Properties> {
public:
using GenericNodeType = GenericNode<Key, Value, Properties>;
using InternalNodeType = InternalNode<Key, Value, Properties>;
using LeafNodeType = LeafNode<Key, Value, Properties>;
using InternalNodeItemPlaceholderType = InternalNodeItemPlaceholder<Key, Value, Properties>;
using InternalNodeItemType = InternalNodeItem<Key, Value, Properties>;
public:
virtual ~InternalNode();
/* node size in bytes (increases with branching factor) */
static std::size_t node_sizeof(std::size_t branching_factor);
/* use when splitting root node for the first time;
* new root node will be leaf->internal.
*
* require: child_1, child_2 are non-empty
*/
static std::unique_ptr<InternalNode> make_2(std::unique_ptr<GenericNodeType> child_1,
std::unique_ptr<GenericNodeType> child_2);
/* Before:
*
* m = mid_ix
* n = src.n_elt - 1
* xa @ [m-1]
* xb @ [m]
* xz @ [n-1]
*
* src.elt_v[]
*
* 0 m-1 m n-1
* +----+-...-+----+----+-...-+----+
* | x0 | ... | xa | xb | ... | xz |
* +----+-...-+----+----+-...-+----+
*
* <----------- n items ----------->
*
* After:
*
* src.elt_v[] new_node.elt_v[]
*
* n-m-1
* 0 m-1 0 v
* +----+-...-+----+ +----+-...-+----+
* | x0 | ... | xa | | xb | | xz |
* +----+-...-+----+ +----+-...-+----+
*
* <--- m items ---> <-- n-m items -->
*/
static std::unique_ptr<InternalNode> annex(std::size_t mid_ix,
InternalNode * src);
/* .elt_v[]
*
* 0 k n-1 with: n <= b = branching factor
* +---+---+- ... -+---+- ... -+---+---+ k = lub(key) in {e1..en}
* | e1| e2| | ek| | | en|
* +---+---+- ... -+---+- ... -+---+---+
*
* retval.first: true if key already present in tree. implies lub_ix_recd.second >= 1
* retval.second: upper bound (strict) index position in .elt_v[] of key
*
* Cost: O(log(bf)) key comparisons
*/
std::size_t find_lub_ix(Key const & key) const;
/* warning: requires key is present! */
std::size_t find_ix(Key const & key) const { return this->find_lub_ix(key) - 1; }
/* O(bf), but does not rely on key invariants. */
std::size_t locate_child_by_address(GenericNodeType const * target_child) const;
InternalNodeItemType & lookup_elt(std::size_t i) { return *(reinterpret_cast<InternalNodeItemType *>(&(elt_v_[i]))); }
InternalNodeItemType const & lookup_elt(std::size_t i) const { return *(reinterpret_cast<InternalNodeItemType const *>(&(elt_v_[i]))); }
FindNodeResult<GenericNodeType> find_child(Key const & key);
/* insert node at position ix; moving items starting in .elt_v[ix] one slot to the right */
void insert_node(std::size_t ix, std::unique_ptr<GenericNodeType> child, bool debug_flag);
/* remove node at position ix; moving items starting .elt_v[ix+1] one slot to the left;
* if target is a leaf node, also remove from prev_leafnode/next_leafnode list
*/
void remove_node(std::size_t ix, bool debug_flag);
/* redistribute last n items from left-hand sibling lh to this internal node */
void prepend_from_lh_sibling(InternalNode * lh, std::size_t n, bool debug_flag);
/* redistribute first n items from right-hand sibling rh to this internal node */
void append_from_rh_sibling(std::size_t n, InternalNode * rh);
void append_rh_sibling(InternalNode * rh) { this->append_from_rh_sibling(rh->n_elt(), rh); }
/* returns new node with upper half of original element vector (i.e. of this.elt_v[]);
* original updated to retain lower half
*/
std::unique_ptr<InternalNode> split_internal();
void set_glb_key(Key key) { this->lookup_elt(0).set_key(key); }
/* memory for InternalNode instances is always created using new[],
* so required to use delete[] to deallocate
*/
void operator delete (void * mem) noexcept { ::operator delete[](mem); }
// ----- inherited from GenericNode -----
virtual Key const & glb_key() const override { return this->lookup_elt(0).key(); }
virtual std::size_t verify_helper(InternalNode const * parent,
bool with_lub_flag,
Key const & lub_key,
LeafNodeType const * lh_leaf,
LeafNodeType const * rh_leaf) const override;
virtual void verify_glb_key(Key const & key) const override;
/* find in subtree_arg the leftmost leaf node (i.e. leaf node with smallest key) */
virtual FindNodeResult<LeafNodeType> find_min_leaf_node() override;
/* find in subtree_arg the rightmost leaf node (i.e. leaf node with largest key) */
virtual FindNodeResult<LeafNodeType> find_max_leaf_node() override;
private:
explicit InternalNode(std::size_t branching_factor);
private:
#ifdef OBSOLETE
/* total #of elements in this subtree */
std::size_t size_ = 0;
#endif
/* flexible array; actual size will be .branching_factor().
*
* .elt_v[i] is created/destroyed as an InternalNodeItemType with non-trivial ctor/dtor.
* we must declare member using POD placeholder to satisfy flexible array rules
*
* invariant:
* - with branching factor b, so range for .elt_v[] is 0 .. b-1:
* - .elt_v[j].child.ptr is null -> {.elt_v[j+1].child.ptr .. .elt_v[b-1].child.ptr} are also null
*/
InternalNodeItemPlaceholderType elt_v_[];
}; /*InternalNode*/
template <typename Key, typename Value, typename Properties>
InternalNode<Key, Value, Properties>::~InternalNode() {
/* since we're using flexible array for .elt_v[], need to manually run destructors */
for (std::size_t i=0, n=this->branching_factor_; i<n; ++i) {
this->lookup_elt(i).~InternalNodeItemType();
}
/* hygiene */
BplusTreeUtil<Key, Value, Properties>::node_clear_size(this);
this->n_elt_ = 0;
this->branching_factor_ = 0;
} /*dtor*/
template <typename Key, typename Value, typename Properties>
std::size_t
InternalNode<Key, Value, Properties>::node_sizeof(std::size_t branching_factor) {
return (sizeof(InternalNode)
+ (branching_factor
* sizeof(InternalNodeItemType)));
} /*node_sizeof*/
template <typename Key, typename Value, typename Properties>
std::unique_ptr<InternalNode<Key, Value, Properties>>
InternalNode<Key, Value, Properties>::make_2(std::unique_ptr<GenericNodeType> child_1,
std::unique_ptr<GenericNodeType> child_2) {
std::size_t branching_factor = child_1->branching_factor();
std::size_t mem_z = node_sizeof(branching_factor);
std::uint8_t * mem = new std::uint8_t[mem_z];
assert(child_1->n_elt() > 0);
assert(child_2->n_elt() > 0);
std::unique_ptr<InternalNode> retval(new (mem) InternalNode(branching_factor));
child_1->set_parent(retval.get());
child_2->set_parent(retval.get());
retval->assign_size(BplusTreeUtil<Key, Value, Properties>::get_node_size(child_1.get())
+ BplusTreeUtil<Key, Value, Properties>::get_node_size(child_2.get()));
retval->n_elt_ = 2;
retval->lookup_elt(0) = std::move(InternalNodeItemType(std::move(child_1)));
retval->lookup_elt(1) = std::move(InternalNodeItemType(std::move(child_2)));
return retval;
} /*make_2*/
template <typename Key, typename Value, typename Properties>
std::unique_ptr<InternalNode<Key, Value, Properties>>
InternalNode<Key, Value, Properties>::annex(std::size_t mid_ix,
InternalNode * src)
{
std::size_t branching_factor = src->branching_factor();
std::size_t mem_z = node_sizeof(branching_factor);
std::uint8_t * mem = new std::uint8_t[mem_z];
std::unique_ptr<InternalNode> new_node(new (mem) InternalNode(branching_factor));
std::size_t hi_ix = src->n_elt();
new_node->n_elt_ = hi_ix - mid_ix;
std::size_t annex_z = 0;
/* annexing upper-half of *src into new_node */
for (std::size_t i = 0, n = hi_ix - mid_ix; i < n; ++i) {
InternalNodeItemType & src_slot = src->lookup_elt(mid_ix + i);
InternalNodeItemType & new_slot = new_node->lookup_elt(i);
annex_z += BplusTreeUtil<Key, Value, Properties>::get_node_size(src_slot.child());
new_slot = std::move(src->lookup_elt(mid_ix + i));
new_slot.child()->set_parent(new_node.get());
}
new_node->assign_size(annex_z);
/* ordinal_disabled: noop
* ordinal_enabled: bookkeeping for src.size (+ new_node.size, see above)
*/
src->assign_size(BplusTreeUtil<Key, Value, Properties>::get_node_size(src) - annex_z);
src->n_elt_ = mid_ix;
return new_node;
} /*annex*/
template <typename Key, typename Value, typename Properties>
std::size_t
InternalNode<Key, Value, Properties>::find_lub_ix(Key const & key) const {
if (key < this->lookup_elt(0).key())
return 0;
std::size_t lo = 0;
std::size_t hi = this->n_elt_;
while (lo + 1 < hi) {
std::size_t mid = lo + (hi - lo) / 2;
if (key < this->lookup_elt(mid).key())
hi = mid;
else
lo = mid;
}
return hi;
} /*find_lub_ix*/
template <typename Key, typename Value, typename Properties>
std::size_t
InternalNode<Key, Value, Properties>::locate_child_by_address(GenericNodeType const * target_child) const {
for (std::size_t ix = 0; ix < this->n_elt_; ++ix) {
if (this->lookup_elt(ix).child() == target_child)
return ix;
}
return static_cast<std::size_t>(-1);
} /*locate_child_by_address*/
template <typename Key, typename Value, typename Properties>
FindNodeResult<LeafNode<Key, Value, Properties>>
InternalNode<Key, Value, Properties>::find_min_leaf_node() {
FindNodeResult<GenericNodeType> findresult(0, this);
while (findresult.node() && (findresult.node()->node_type() == NodeType::internal)) {
std::size_t min_ix = 0;
findresult = FindNodeResult<GenericNodeType>(min_ix,
(reinterpret_cast<InternalNodeType *>(findresult.node()))
->lookup_elt(min_ix /*leftmost child*/).child());
}
/* findresult.node()->node_type() == NodeType::leaf (if non-null) */
if (!findresult.node()) {
assert(false);
return FindNodeResult<LeafNodeType>();
}
assert(findresult.node()->node_type() == NodeType::leaf);
return FindNodeResult<LeafNodeType>(findresult.ix(),
reinterpret_cast<LeafNodeType *>(findresult.node()));
} /*find_min_leaf_node*/
template <typename Key, typename Value, typename Properties>
FindNodeResult<LeafNode<Key, Value, Properties>>
InternalNode<Key, Value, Properties>::find_max_leaf_node() {
FindNodeResult<GenericNodeType> findresult(0, this);
while (findresult.node() && (findresult.node()->node_type() == NodeType::internal)) {
std::size_t max_ix = findresult.node()->n_elt() - 1;
findresult = FindNodeResult<GenericNodeType>
(max_ix,
(reinterpret_cast<InternalNodeType *>(findresult.node()))
->lookup_elt(max_ix /*rightmost child*/).child());
}
/* findresult.node()->node_type() == NodeType::leaf (if non-null) */
if (!findresult.node()) {
assert(false);
return FindNodeResult<LeafNodeType>();
}
assert(findresult.node()->node_type() == NodeType::leaf);
return FindNodeResult<LeafNodeType>(findresult.ix(),
reinterpret_cast<LeafNodeType *>(findresult.node()));
} /*find_max_leaf_node*/
template <typename Key, typename Value, typename Properties>
FindNodeResult<GenericNode<Key, Value, Properties>>
InternalNode<Key, Value, Properties>::find_child(Key const & key) {
std::size_t lub_ix = this->find_lub_ix(key);
if (lub_ix > 0)
--lub_ix;
return FindNodeResult<GenericNodeType>(lub_ix, this->lookup_elt(lub_ix).child());
} /*find_child*/
template <typename Key, typename Value, typename Properties>
void
InternalNode<Key, Value, Properties>::insert_node(std::size_t ix, std::unique_ptr<GenericNodeType> child, bool debug_flag)
{
using xo::scope;
using xo::tostr;
using xo::xtag;
scope log(XO_DEBUG(debug_flag),
xtag("self", this),
xtag("n_elt", this->n_elt()),
xtag("bf", this->branching_factor()),
xtag("ix", ix),
xtag("child", child.get()));
if (this->n_elt_ >= this->branching_factor()) {
assert(false);
throw std::runtime_error(tostr("InternalNode::insert_node: node already full",
xtag("node.n_elt", this->n_elt()),
xtag("branching_factor", this->branching_factor())));
}
if (ix > this->n_elt_) {
assert(false);
throw std::runtime_error(tostr("InternalNode::insert_node: insert position out of range",
xtag("ix", ix),
xtag("node.n_elt", this->n_elt()),
xtag("bf", this->branching_factor())));
}
std::size_t pos_ix = this->n_elt_;
while (pos_ix > ix) {
this->lookup_elt(pos_ix) = std::move(this->lookup_elt(pos_ix - 1));
--pos_ix;
}
/* WARNING: don't update .size here
* in practice we use .insert_node() when introducing a single new key/value pair;
* when we use .insert_node() we split an existing node,
* and actually just want to increment .size.
*
* We leave this to caller (e.g. BplusTree.internal_insert_aux())
* because in that context can see the upstream split
*/
// this->size_ += child->n_elt();
++(this->n_elt_);
child->set_parent(this);
this->lookup_elt(ix) = InternalNodeItemType(std::move(child));
} /*insert_node*/
template <typename Key, typename Value, typename Properties>
void
InternalNode<Key, Value, Properties>::remove_node(std::size_t ix, bool debug_flag) {
using xo::scope;
using xo::tostr;
using xo::xtag;
scope log(XO_DEBUG(debug_flag),
xtag("self", this),
xtag("n_elt", this->n_elt()),
xtag("bf", this->branching_factor()),
xtag("ix", ix));
if (ix >= this->n_elt_) {
assert(false);
throw std::runtime_error(tostr("InternalNode::remove_node: target position out of range",
xtag("ix", ix),
xtag("node.n_elt", this->n_elt()),
xtag("bf", this->branching_factor())));
}
std::size_t pos_ix = ix;
std::size_t end_ix = this->n_elt_ - 1;
{
InternalNodeItemType & target_item = this->lookup_elt(pos_ix);
/* WARNING: don't update .size here
* in practice we use .remove_node() when deleting a single new key/value pair;
* when we use .remove_node() we merge existing nodes,
* and actually just want to decrement .size.
*
* We leave this to caller (e.g. BplusTree.internal_remove_aux())
* because in that context can see the upstream merge
*/
//this->size_ -= target_item.child()->size();
target_item.notify_remove();
}
while (pos_ix < end_ix) {
//scope x1("loop", debug_flag);
//x1(xtag("pos_ix", pos_ix));
this->lookup_elt(pos_ix) = std::move(this->lookup_elt(pos_ix + 1));
++pos_ix;
}
--(this->n_elt_);
} /*remove_node*/
template <typename Key, typename Value, typename Properties>
void
InternalNode<Key, Value, Properties>::prepend_from_lh_sibling(InternalNode * lh, std::size_t n, bool debug_flag) {
using xo::scope;
using xo::xtag;
scope log(XO_DEBUG(debug_flag),
xtag("@", this), xtag("n", n));
if (this->n_elt() + n > this->branching_factor()) {
assert(false);
throw std::runtime_error(tostr("InternalNode.prepend_from_lh_sibling: expected combined #elt <= bf",
xtag("self.n_elt", this->n_elt()),
xtag("n", n),
xtag("bf", this->branching_factor())));
}
std::size_t n_lh = lh->n_elt();
std::size_t n_rh = this->n_elt();
/* move elts in *this to the right n steps (starting from the end) */
for (std::size_t ixp1 = this->n_elt(); ixp1 > 0; --ixp1) {
std::size_t ix = ixp1 - 1;
//x.log("move", xtag("ix", ix), xtag("ix+n", ix+n));
this->lookup_elt(ix + n) = std::move(this->lookup_elt(ix));
}
std::size_t xfer_z = 0;
/* xfer n elts from upper end of lh, to lower end of *this */
for (std::size_t ix = 0; ix < n; ++ix) {
//x.log("fill", xtag("ix", ix), xtag("n_lh-n+ix", n_lh - n + ix));
InternalNodeItemType & lh_sibling_item = lh->lookup_elt(n_lh - n + ix);
xfer_z += BplusTreeUtil<Key, Value, Properties>::get_node_size(lh_sibling_item.child());
this->lookup_elt(ix) = std::move(lh_sibling_item);
/* + fixup parent pointer */
this->lookup_elt(ix).child()->set_parent(this);
}
BplusTreeUtil<Key, Value, Properties>::node_add_size(this, xfer_z);
BplusTreeUtil<Key, Value, Properties>::node_sub_size(lh, xfer_z);
this->n_elt_ += n;
lh->n_elt_ -= n;
log && log(xtag("this.glb_key", this->glb_key()),
xtag("this[0].key", this->lookup_elt(0).key()));
log.end_scope();
} /*prepend_from_lh_sibling*/
template <typename Key, typename Value, typename Properties>
void
InternalNode<Key, Value, Properties>::append_from_rh_sibling(std::size_t n, InternalNode * rh) {
using xo::xtag;
if (this->n_elt() + n > this->branching_factor()) {
assert(false);
throw std::runtime_error(tostr("InternalNode.append_from_rh_sibling: expected combined #elt <= bf",
xtag("self.n_elt", this->n_elt()),
xtag("n", n),
xtag("bf", this->branching_factor())));
}
std::size_t n_lh = this->n_elt();
std::size_t xfer_z = 0;
for (std::size_t ix = 0; ix < n; ++ix) {
InternalNodeItemType & rh_sibling_item = rh->lookup_elt(ix);
xfer_z += BplusTreeUtil<Key, Value, Properties>::get_node_size(rh_sibling_item.child());
this->lookup_elt(n_lh + ix) = std::move(rh_sibling_item);
/* + fixup parent pointer */
this->lookup_elt(n_lh + ix).child()->set_parent(this);
}
BplusTreeUtil<Key, Value, Properties>::node_add_size(this, xfer_z);
this->n_elt_ += n;
/* shuffle remaining members of rh sibling n items to the left */
for (std::size_t ix = 0; ix < rh->n_elt() - n; ++ix) {
rh->lookup_elt(ix) = std::move(rh->lookup_elt(ix + n));
}
BplusTreeUtil<Key, Value, Properties>::node_sub_size(rh, xfer_z);
rh->n_elt_ -= n;
} /*append_from_rh_sibling*/
template <typename Key, typename Value, typename Properties>
std::unique_ptr<InternalNode<Key, Value, Properties>>
InternalNode<Key, Value, Properties>::split_internal() {
std::size_t n_elt = this->n_elt_;
std::size_t mid_ix = n_elt / 2;
return InternalNode::annex(mid_ix, this);
} /*split_internal*/
template <typename Key, typename Value, typename Properties>
std::size_t
InternalNode<Key, Value, Properties>::verify_helper(InternalNode const * parent,
bool with_lub_flag,
Key const & lub_key,
LeafNodeType const * lh_leaf,
LeafNodeType const * rh_leaf) const
{
using xo::tostr;
using xo::xtag;
std::size_t retval = 0;
/* verify immediate parent pointer is correct */
if (this->parent() != parent) {
throw std::runtime_error(tostr("InternalNode::verify_helper"
": expected parent pointer to refer to actual parent",
xtag("stored_parent", this->parent()),
xtag("actual_parent", parent)));
}
std::size_t n = this->n_elt_;
/* verify all children have same NodeType (either all= internal or all= leaf) */
NodeType target_child_node_type = NodeType::leaf;
if (n > 0)
target_child_node_type = this->lookup_elt(0).child()->node_type();
LeafNodeType const * prev_lh_leaf = lh_leaf;
for (std::size_t i=0; i < n; ++i) {
/* check consistent node type */
NodeType i_nodetype = this->lookup_elt(i).child()->node_type();
if ((i > 0) && (i_nodetype != target_child_node_type)) {
throw std::runtime_error(tostr("InternalNode::verify_helper"
": expected all children to share the same node type",
xtag("i", i),
xtag("elt[0].node_type", target_child_node_type),
xtag("elt[i].node_type", i_nodetype)));
}
/* nested verify on child subtrees */
InternalNodeItemType const & i_elt = this->lookup_elt(i);
LeafNodeType const * next_lh_leaf = ((i+1 < n)
? this->lookup_elt(i+1).child()->find_min_leaf_node().node()
: rh_leaf);
retval += i_elt.child()->verify_helper(this,
(i+1 < n) ? true : with_lub_flag,
(i+1 < n) ? this->lookup_elt(i+1).key() : lub_key,
prev_lh_leaf,
next_lh_leaf);
prev_lh_leaf = i_elt.child()->find_max_leaf_node().node();
}
if (Properties::ordinal_tag_value() == tags::ordinal_enabled) {
/* verify stored subtree size is consistent with children's */
std::size_t sum_z = 0;
for (std::size_t i=0, n=this->n_elt_; i < n; ++i) {
InternalNodeItemType const & elt = this->lookup_elt(i);
sum_z += BplusTreeUtil<Key, Value, Properties>::get_node_size(elt.child());
}
std::size_t self_z = BplusTreeUtil<Key, Value, Properties>::get_node_size(this);
if (sum_z != self_z) {
throw std::runtime_error(tostr("InternalNode::verify_helper",
": inconsistent subtree size",
xtag("node", this),
xtag("treez[stored]", self_z),
xtag("treez[computed]", sum_z)));
}
}
/* verify stored glb_key is correct */
for (std::size_t i=0, n=this->n_elt_; i < n; ++i) {
InternalNodeItemType const & elt = this->lookup_elt(i);
elt.child()->verify_glb_key(elt.key());
}
/* verify locally stored keys appear in sorted order */
for (std::size_t i=1; i < n; ++i) {
InternalNodeItemType const & prev = this->lookup_elt(i-1);
InternalNodeItemType const & elt = this->lookup_elt(i);
if (prev.key() < elt.key()) {
;
} else {
throw std::runtime_error(tostr("InternalNode::verify_helper"
": expected local keys in strictly increasing order",
xtag("i", i),
xtag("key(i-1)", prev.key()),
xtag("key(i)", elt.key())));
}
}
/* verify highest stored key before parent-supplied upper bound */
if (with_lub_flag) {
if (this->lookup_elt(n-1).key() < lub_key) {
;
} else {
throw std::runtime_error(tostr("InternalNode::verify_helper"
": expected highest local key before parent-supplied lub key",
xtag("n", n),
xtag("key(n-1)", this->lookup_elt(n-1).key()),
xtag("lub_key", lub_key)));
}
}
return retval;
} /*verify_helper*/
template <typename Key, typename Value, typename Properties>
void
InternalNode<Key, Value, Properties>::verify_glb_key(Key const & key) const {
InternalNodeItemType const & elt = this->lookup_elt(0);
elt.child()->verify_glb_key(key);
} /*verify_glb_key*/
template <typename Key, typename Value, typename Properties>
InternalNode<Key, Value, Properties>::InternalNode(std::size_t branching_factor)
: InternalNodeShim<Key, Value, Properties>{NodeType::internal, branching_factor}
{
/* must invoke ctor explicitly for each .elt_v[i].
* compiler doesn't know extent of .elt_v[], since it's a flexible array
*/
for (std::size_t i = 0; i < branching_factor; ++i) {
/* using placement new to force ctor call inside flexible array */
new (&(this->lookup_elt(i))) InternalNodeItemType();
}
} /*ctor*/
} /*namespace tree*/
} /*namespace xo*/
/* end InternalNode.hpp */

View file

@ -0,0 +1,355 @@
/* @file Iterator.hpp */
#pragma once
#include "IteratorUtil.hpp"
#include "LeafNode.hpp"
#include "xo/indentlog/print/tostr.hpp"
namespace xo {
namespace tree {
namespace detail {
/* TODO: move to tree/IteratorUtil.hpp */
/* placeholder - specialize on isConst */
template <typename Key,
typename Value,
typename Properties,
bool isConst>
struct NodeTypeTraits { using LeafNodeType = void; };
/* non-const node pointer */
template <typename Key,
typename Value,
typename Properties>
struct NodeTypeTraits<Key, Value, Properties, false /*!isConst*/> {
using NativeLeafNodeType = LeafNode<Key, Value, Properties>;
using LeafNodeType = NativeLeafNodeType;
using NativeContentsType = typename LeafNodeType::ContentsType;
using LeafNodePtrType = LeafNodeType *;
};
/* const node pointer */
template <typename Key,
typename Value,
typename Properties>
struct NodeTypeTraits<Key, Value, Properties, true /*isConst*/> {
using NativeLeafNodeType = LeafNode<Key, Value, Properties>;
using LeafNodeType = NativeLeafNodeType const;
using NativeContentsType = typename LeafNodeType::ContentsType const;
using LeafNodePtrType = LeafNodeType const *;
};
/* shared between const and non-const b+ tree iterators
*
* +------------+
* |IteratorBase|
* | .dirn |
* | .location |
* | .leafnode |
* | .ix |
* +------------+
* ^
* | isa +-------------+
* +-----------|ConstIterator|
* | | .operator++ |
* | | .operator-- |
* | +-------------+
* |
* | isa +--------+
* +-----------|Iterator|
* +--------+
*/
template <typename Key,
typename Value,
typename Properties,
bool isConst>
class IteratorBase {
public:
using Traits = NodeTypeTraits<Key, Value, Properties, isConst>;
using BpLeafNodePtrType = typename Traits::LeafNodePtrType;
using BpLeafNodeItemType = typename Traits::LeafNodeType::LeafNodeItemType;
using NativeContentsType = typename Traits::NativeContentsType;
protected:
IteratorBase() = default;
IteratorBase(IteratorDirection dirn, IteratorLocation loc, BpLeafNodePtrType leaf, std::size_t ix)
: dirn_{dirn}, location_{loc}, leafnode_{leaf}, ix_{ix} {}
IteratorBase(IteratorBase const &) = default;
static IteratorBase prebegin_aux(BpLeafNodePtrType node) {
return IteratorBase(ID_Forward, IL_BeforeBegin, node, 0 /*ix*/);
}
static IteratorBase begin_aux(BpLeafNodePtrType node) {
return IteratorBase(ID_Forward,
(node ? IL_Regular : IL_AfterEnd),
node,
0 /*ix*/);
}
static IteratorBase end_aux(BpLeafNodePtrType node) {
return IteratorBase(ID_Forward,
IL_AfterEnd,
node,
0 /*ix*/);
}
static IteratorBase rprebegin_aux(BpLeafNodePtrType node) {
return IteratorBase(ID_Reverse,
IL_AfterEnd,
node,
0 /*ix*/);
}
static IteratorBase rbegin_aux(BpLeafNodePtrType node) {
return IteratorBase(ID_Reverse,
(node ? IL_Regular : IL_BeforeBegin),
node,
(node ? node->n_elt() - 1: 0));
}
static IteratorBase rend_aux(BpLeafNodePtrType node) {
return IteratorBase(ID_Reverse,
IL_BeforeBegin,
node,
0 /*ix*/);
}
public:
IteratorLocation location() const { return location_; }
BpLeafNodePtrType node() const { return leafnode_; }
BpLeafNodeItemType const * item_addr() const { return &(leafnode_->lookup_elt(this->ix_)); }
NativeContentsType const & operator*() const {
this->check_regular();
return this->leafnode_->lookup_elt(this->ix_).kv_pair();
} /*operator**/
NativeContentsType const * operator->() const {
return &(this->operator*());
} /*operator->*/
bool is_sentinel() const { return (this->location_ != IL_Regular); }
bool is_dereferenceable() const { return !this->is_sentinel(); }
operator bool() const { return this->is_deferenceable(); }
bool operator==(IteratorBase const & x) const {
return (this->location_ == x.location_) && (this->leafnode_ == x.leafnode_) && (this->ix_ == x.ix_);
}
bool operator!=(IteratorBase const & x) const {
return (this->location_ != x.location_) || (this->leafnode_ != x.leafnode_) || (this->ix_ != x.ix_);
}
void print(std::ostream & os) const {
using xo::xtag;
os << "<bptree-iterator"
<< xtag("dirn", dirn_)
<< xtag("loc", location_)
<< xtag("leaf", leafnode_)
<< xtag("ix", ix_)
<< ">";
} /*print*/
/* pre-increment */
IteratorBase & operator++() {
return ((this->dirn_ == ID_Forward)
? this->next_step()
: this->prev_step());
} /*operator++*/
/* pre-decrement */
IteratorBase & operator--() {
return ((this->dirn_ == ID_Forward)
? this->prev_step()
: this->next_step());
} /*operator--*/
private:
IteratorBase & next_step() {
switch(this->location_) {
case IL_BeforeBegin:
/* .leafnode is leftmost node in tree */
this->location_ = IL_Regular;
break;
case IL_Regular:
{
/* #of elts in node, not #of elts in tree! */
std::size_t n_elt = this->leafnode_->n_elt();
if (this->ix_ + 1 < n_elt) {
++(this->ix_);
} else if (this->leafnode_->next_leafnode()) {
this->leafnode_ = this->leafnode_->next_leafnode();
this->ix_ = 0;
} else {
/* preserve .leafnode:
* (a) for == comparison w/ .end() iterator
* (b) so we can iterate backwards from end position
*/
//this->leafnode_ = this->leafnode_->next_leafnode();
this->location_ = IL_AfterEnd;
this->ix_ = 0;
}
}
break;
case IL_AfterEnd:
break;
}
return *this;
} /*next_step*/
IteratorBase & prev_step() {
switch(this->location_) {
case IL_BeforeBegin:
break;
case IL_Regular:
if (this->ix_ > 0) {
--(this->ix_);
} else if (this->leafnode_->prev_leafnode()) {
this->leafnode_ = this->leafnode_->prev_leafnode();
this->ix_ = this->leafnode_->n_elt() - 1;
} else /* .ix == 0 && .leafnode.prev_leafnode == nullptr */ {
/* preserve .leafnode:
* (a) for == comparison w/ .prebegin() iterator
* (b) so iterator is reversible; can iterate forwards from prebegin position
*/
this->location_ = IL_BeforeBegin;
}
break;
case IL_AfterEnd:
/* .leafnode is rightmost node in tree */
this->location_ = IL_Regular;
this->ix_ = this->leafnode_->n_elt() - 1;
break;
}
return *this;
} /*prev_step*/
private:
void check_regular() const {
using xo::tostr;
using xo::xtag;
if (this->location_ != IL_Regular) {
throw std::runtime_error(tostr("bplustree iterator: cannot deref iterator"
" in sentinel state",
xtag("loc", this->location_)));
}
} /*check_regular*/
private:
/* ID_Forward forward iterator
* ID_Reverse reverse iterator
*/
IteratorDirection dirn_ = ID_Forward;
/* IL_BeforeBegin | IL_Regular | IL_AfterEnd
*
* operator++ operator++
* IL_BeforeBegin --------------> IL_Regular --------------> IL_AfterEnd
* /-> -\
* | |
* \----------------/
* operator++
*
* operator-- operator--
* IL_BeforeBegin <------------- IL_Regular <-------------- IL_AfterEnd
* /-- <-\
* | |
* \-----------------/
* operator--
*
*
*/
IteratorLocation location_ = IL_AfterEnd;
/* .location .leafnode
* IL_BeforeBegin BplusTree.leafnode_begin (leftmost leaf node)
* IL_Regular any leaf node reachable from BplusTree.leafnode_begin
* (or equivalently from BplusTree.leafnode_end)
* IL_AfterEnd BplusTree.leafnode_end (rightmost leaf node)
*/
BpLeafNodePtrType leafnode_ = nullptr;
/* index position within .leafnode;
* 0 when .location is IL_BeforeBegin | IL_AfterEnd
*/
std::size_t ix_ = 0;
}; /*IteratorBase*/
template <typename Key,
typename Value,
typename Properties>
class ConstIterator : public IteratorBase<Key, Value, Properties, true /*isConst*/> {
public:
using iterator_concept = std::bidirectional_iterator_tag;
using BpIteratorBase = IteratorBase<Key, Value, Properties, true /*isConst*/>;
using BpLeafNodePtrType = typename BpIteratorBase::BpLeafNodePtrType;
public:
ConstIterator() = default;
ConstIterator(IteratorDirection dirn, IteratorLocation loc, BpLeafNodePtrType leaf, std::size_t ix)
: IteratorBase<Key, Value, Properties, true /*isConst*/>(dirn, loc, leaf, ix) {}
ConstIterator(ConstIterator const & x) = default;
ConstIterator(BpIteratorBase const & x) : BpIteratorBase(x) {}
ConstIterator(BpIteratorBase && x) : BpIteratorBase{std::move(x)} {}
static ConstIterator prebegin_aux(BpLeafNodePtrType leaf) { return BpIteratorBase::prebegin_aux(leaf); }
static ConstIterator begin_aux(BpLeafNodePtrType leaf) { return BpIteratorBase::begin_aux(leaf); }
static ConstIterator end_aux(BpLeafNodePtrType leaf) { return BpIteratorBase::end_aux(leaf); }
static ConstIterator rprebegin_aux(BpLeafNodePtrType leaf) { return BpIteratorBase::rprebegin_aux(leaf); }
static ConstIterator rbegin_aux(BpLeafNodePtrType leaf) { return BpIteratorBase::rbegin_aux(leaf); }
static ConstIterator rend_aux(BpLeafNodePtrType leaf) { return BpIteratorBase::rend_aux(leaf); }
/* pre-increment */
ConstIterator & operator++() {
BpIteratorBase::operator++();
return *this;
} /*operator++*/
/* post-increment */
ConstIterator operator++(int) {
ConstIterator retval = *this;
++(*this);
return retval;
} /*operator++*/
/* pre-decrement */
ConstIterator & operator--() {
BpIteratorBase::operator--();
return *this;
} /*operator--*/
/* post-decrement */
ConstIterator operator--(int) {
ConstIterator retval = *this;
--(*this);
return retval;
} /*operator--*/
}; /*ConstIterator*/
} /*namespace detail*/
template <typename Key,
typename Value,
typename Properties,
bool IsConst>
inline std::ostream &
operator<<(std::ostream & os,
detail::IteratorBase<Key, Value, Properties, IsConst> const & iter)
{
iter.print(os);
return os;
} /*operator<<*/
} /*namespace tree*/
} /*namespace xo*/
/* end Iterator.hpp */

View file

@ -0,0 +1,56 @@
/* @file IteratorUtil.hpp */
#pragma once
#include <ostream>
namespace xo {
namespace tree {
namespace detail {
enum IteratorDirection {
/* ID_Forward. forward iterator
* ID_Reverse. reverse iterator
*/
ID_Forward,
ID_Reverse
}; /*IteratorDirection*/
/* specify iterator location relative to a particular b+ tree node */
enum IteratorLocation {
/*
* IL_BeforeBegin. if non-empty tree, Iterator.node is the first node
* in the tree (the one with smallest key),
* and iterator refers to the location
* "one before" that first node.
* IL_Regular. iterator refers to member of the tree
* given by Iterator.node
* IL_AfterEnd. if non-empty tree, Iterator.node is the last node
* in the tree (the one with largest key),
* and iterator refers the the location
* "one after" that last node.
*/
IL_BeforeBegin,
IL_Regular,
IL_AfterEnd
}; /*IteratorLocation*/
static inline char const * iterator_location_descr(IteratorLocation x) {
switch(x) {
case IL_BeforeBegin: return "before-begin";
case IL_Regular: return "regular";
case IL_AfterEnd: return "after-end";
default: return "???";
}
} /*iteerator_location_descr*/
inline std::ostream &
operator<<(std::ostream & os, IteratorLocation x) {
os << iterator_location_descr(x);
return os;
} /*operator<<*/
} /*namespace detail*/
} /*namespace tree*/
} /*namespace xo*/
/* end IteratorUtil.hpp */

View file

@ -0,0 +1,684 @@
/* @file LeafNode.hpp */
#pragma once
#include "GenericNode.hpp"
#include "xo/indentlog/scope.hpp"
#include <cassert>
namespace xo {
namespace tree {
// ----- LeafNodeItem -----
template <typename Key, typename Value, typename Properties>
using LeafNodeItem = NodeItem<NodeType::leaf, Key, Value, Properties>;
/* - define for symmetry with NodeItem<Key, Properties>
* - LeafNodeItem doesn't contain a child pointer;
* it belongs inside a leaf mode, which by definition doesn't have children
*/
template <typename Key, typename Value, typename Properties>
struct NodeItem<NodeType::leaf, Key, Value, Properties> {
public:
using ContentsType = std::pair<Key, Value>;
public:
NodeItem() = default;
NodeItem(std::pair<Key, Value> kv) : kv_pair_{std::move(kv)} {}
std::pair<Key, Value> const & kv_pair() const { return kv_pair_; }
Key const & key () const { return kv_pair_.first; }
Value const & value() const { return kv_pair_.second; }
void assign_value(Value x) { kv_pair_.second = std::move(x); }
private:
/* key+value pair */
std::pair<Key, Value> kv_pair_;
}; /*NodeItem*/
/* struct with same size as LeafNodeItem<Key, Value, Properties>, but POD + with no ctor/dtor */
template <typename Key, typename Value, typename Properties>
using LeafNodeItemPlaceholder = NodeItemPlaceholder<NodeType::leaf, Key, Value, Properties>;
template <typename Key, typename Value, typename Properties, tags::ordinal_tag OrdinalTag = Properties::ordinal_tag_value()>
struct LeafNodeShim : public GenericNode<Key, Value, Properties>
{
LeafNodeShim(NodeType ntype, std::size_t branching_factor) : GenericNode<Key, Value, Properties>(ntype, branching_factor) {}
/* ordinal_enabled: LeafNode will provide .size(): inherits+overrides GenericNodeBase.size() */
};
template <typename Key, typename Value, typename Properties>
struct LeafNodeShim<Key, Value, Properties, tags::ordinal_disabled> : public GenericNode<Key, Value, Properties>
{
LeafNodeShim(NodeType ntype, std::size_t branching_factor) : GenericNode<Key, Value, Properties>(ntype, branching_factor) {}
/* ordinal_disabled: LeafNode provides LeafNode::size(), but not used */
virtual std::size_t size() const = 0;
};
// ----- LeafNode -----
/* require:
* - Properties.branching_factor()
*/
template <typename Key, typename Value, typename Properties>
struct LeafNode : public LeafNodeShim<Key, Value, Properties> {
public:
using GenericNodeType = GenericNode<Key, Value, Properties>;
using LeafNodeType = LeafNode<Key, Value, Properties>;
using LeafNodeItemType = LeafNodeItem<Key, Value, Properties>;
using LeafNodeItemPlaceholderType = LeafNodeItemPlaceholder<Key, Value, Properties>;
using InternalNodeType = InternalNode<Key, Value, Properties>;
using ContentsType = typename LeafNodeItemType::ContentsType;
public:
virtual ~LeafNode();
/* node size in bytes (increases with branching factor) */
static std::size_t node_sizeof(std::size_t branching_factor);
/* named ctor idiom. enforce heap allocation + unique_ptr wrapper */
static std::unique_ptr<LeafNode> make(std::pair<Key const, Value> kv_pair,
Properties const & properties);
/* create+return new leaf node that contains all the items in *src from position [lo_ix, hi_ix),
* after this operation size of *src is reduced by (hi_ix - lo_ix)
*/
static std::unique_ptr<LeafNode> annex(std::size_t lo_ix,
std::size_t hi_ix,
LeafNode * src);
LeafNode * prev_leafnode() const { return prev_leafnode_; }
LeafNode * next_leafnode() const { return next_leafnode_; }
/* .first: true if key in tree already
* .second: index position of (strict) least upper bound in .elt_v[]
* if .n_elt, key has no upper bound in this node
*/
std::pair<bool, std::size_t> find_lub_ix(Key const & key) const;
LeafNodeItemType & lookup_elt(std::size_t i) { return *(reinterpret_cast<LeafNodeItemType *>(&(this->elt_v_[i]))); }
LeafNodeItemType const & lookup_elt(std::size_t i) const { return *(reinterpret_cast<LeafNodeItemType const *>(&(this->elt_v_[i]))); }
void assign_leaf_value(std::size_t elt_ix, Value value) {
assert(elt_ix < this->n_elt_);
this->lookup_elt(elt_ix).assign_value(std::move(value));
} /*assign_leaf_value*/
/* assign precdeing leaf node (= LH sibling if share same parent) */
void assign_prev_leafnode(LeafNode * x) { prev_leafnode_ = x; }
void assign_next_leafnode(LeafNode * x) { next_leafnode_ = x; }
/* insert new leaf at position ix, associating key -> value
* (shuffle existing elements at ix, ix+1.. 1 position to the right)
*/
void insert_leaf_item(std::size_t ix,
std::pair<Key const, Value> const & kv_pair,
bool debug_flag);
/* remove key,value pair at position ix */
void remove_leaf(std::size_t ix, bool debug_flag);
/* append n items from left-hand sibling, as new left-most elements
* require: combined #of items must be at most b = branching factor
*/
void prepend_from_lh_sibling(LeafNode * lh, std::size_t n, bool debug_flag);
/* apepnd n items from right-hand sibling, as new right-most elements
* require: combined #of items must be at most b = branching factor
*/
void append_from_rh_sibling(std::size_t n, LeafNode * rh);
void append_rh_sibling(LeafNode * rh) { this->append_from_rh_sibling(rh->n_elt(), rh); }
/* returns new leaf with lower half of original element vector;
* original updated to retain upper half
*/
std::unique_ptr<LeafNode> split_leaf_lower();
/* returns new leaf with upper half of original element vector;
* original updated to retain lower half
*/
std::unique_ptr<LeafNode> split_leaf_upper();
/* memory for LeafNode instances is always created using new[],
* so required to use delete[] to deallocate
*/
void operator delete (void * mem) noexcept { ::operator delete[](mem); }
// ----- Inherited from GenericNode -----
virtual std::size_t size() const override { return this->n_elt(); }
virtual Key const & glb_key() const override { return this->lookup_elt(0).key(); }
virtual std::size_t verify_helper(InternalNodeType const * parent,
bool with_lub_flag,
Key const & lub_key,
LeafNodeType const * lh_leaf,
LeafNodeType const * rh_leaf) const override;
virtual void verify_glb_key(Key const & key) const override;
virtual FindNodeResult<LeafNodeType> find_min_leaf_node() override;
virtual FindNodeResult<LeafNodeType> find_max_leaf_node() override;
virtual void notify_remove() override;
private:
explicit LeafNode(std::size_t branching_factor);
LeafNode(std::pair<Key const, Value> const & kv_pair,
std::size_t branching_factor);
void assign_siblings(LeafNode * prev, LeafNode * next);
private:
/* previous LeafNode in key order, immediately before (all the keys in) this node.
* use to streamline inorder traversal.
*/
LeafNode * prev_leafnode_ = nullptr;
/* next LeafNode in key order, immediately after (all the keys in) this node.
* streamline inorder traversal.
*/
LeafNode * next_leafnode_ = nullptr;
/* flexible array; actual capacity will be Properties.branching_factor();
* but only members [0 .. n_elt-1] are defined.
*
* actual type of .elt_v[i] is LeafNodeItem<Key, Value, Properties>;
* need to use POD LeafNodeItemPlaceholder<Key, Value, Properties> to satisfy flexible-array rules
*/
LeafNodeItemPlaceholderType elt_v_[];
}; /*LeafNode*/
template <typename Key, typename Value, typename Properties>
LeafNode<Key, Value, Properties>::~LeafNode() {
/* since we're using flexible array for .elt_v[], need to manually run destructors */
for (std::size_t i=0, n=this->branching_factor_; i<n; ++i) {
this->lookup_elt(i).~LeafNodeItemType();
}
/* hygiene */
this->n_elt_ = 0;
this->branching_factor_ = 0;
} /*dtor*/
template <typename Key, typename Value, typename Properties>
std::size_t
LeafNode<Key, Value, Properties>::node_sizeof(std::size_t branching_factor) {
/* since we're using flexible array for .elt_v[], need to manually account for it's allocated size */
return (sizeof(LeafNode)
+ (branching_factor
* sizeof(LeafNodeItem<Key, Value, Properties>)));
} /*node_sizeof*/
template <typename Key, typename Value, typename Properties>
std::unique_ptr<LeafNode<Key, Value, Properties>>
LeafNode<Key, Value, Properties>::make(std::pair<Key const, Value> kv_pair,
Properties const & properties)
{
using xo::scope;
using xo::xtag;
std::size_t mem_z = node_sizeof(properties.branching_factor());
/* storage for LeafNode, including storage cost for flexible array LeafNode.elt_v[] */
std::uint8_t * mem = new std::uint8_t[mem_z];
#ifdef NOT_IN_USE
scope x("LeafNode.make");
x.log(xtag("sizeof(LeafNode)", sizeof(LeafNode)),
xtag("bf", properties.branching_factor()),
xtag("mem_z", mem_z),
xtag("mem", (void *)mem));
#endif
return std::unique_ptr<LeafNode>(new (mem) LeafNode(std::move(kv_pair),
properties.branching_factor()));
} /*make*/
template <typename Key, typename Value, typename Properties>
std::unique_ptr<LeafNode<Key, Value, Properties>>
LeafNode<Key, Value, Properties>::annex(std::size_t lo_ix,
std::size_t hi_ix,
LeafNode * src)
{
using xo::scope;
using xo::xtag;
std::size_t branching_factor = src->branching_factor();
assert(hi_ix >= lo_ix);
assert(hi_ix - lo_ix <= branching_factor);
std::size_t mem_z = node_sizeof(branching_factor);
std::uint8_t * mem = new std::uint8_t[mem_z];
#ifdef NOT_IN_USE
scope x("LeafNode.annex");
x.log(xtag("sizeof(LeafNode)", sizeof(LeafNode)),
xtag("bf", branching_factor),
xtag("mem_z", mem_z),
xtag("mem", (void *)mem));
#endif
std::unique_ptr<LeafNode> new_node(new (mem) LeafNode(branching_factor));
std::size_t old_n = src->n_elt();
new_node->n_elt_ = hi_ix - lo_ix;
std::size_t n_annex = hi_ix - lo_ix;
/* annexing from *src into new_node */
for (std::size_t i = 0; i < n_annex; ++i) {
LeafNodeItemType & new_slot = new_node->lookup_elt(i);
new_slot = std::move(src->lookup_elt(lo_ix + i));
}
/* shuffle over any remaining items in *src starting from hi_ix */
for (std::size_t i = lo_ix; i + n_annex < old_n; ++i) {
LeafNodeItemType & slot = src->lookup_elt(i);
slot = std::move(src->lookup_elt(i + n_annex));
}
src->n_elt_ = old_n - n_annex;
if (lo_ix == 0) {
/* new node builds by taking leftmost elements from src
* -> new node becomes src's predecessor
*/
new_node->assign_siblings(src->prev_leafnode(), src);
} else {
/* new node builds by taking rightmost elements from src
* -> new node becomes src's successor
*/
new_node->assign_siblings(src, src->next_leafnode());
}
return new_node;
} /*annex*/
template <typename Key, typename Value, typename Properties>
std::pair<bool, std::size_t>
LeafNode<Key, Value, Properties>::find_lub_ix(Key const & key) const {
if (key < this->lookup_elt(0).key())
return std::make_pair(false, 0);
/* promise: return value >= 0 */
/* .elt_v[0 .. n_elt-1] are maintained in sorted key order */
std::size_t lo = 0;
std::size_t hi = this->n_elt_;
while (lo + 1 < hi) {
/* desired child item will be in range [lo, hi) */
std::size_t mid = lo + (hi - lo) / 2;
if (key < this->lookup_elt(mid).key())
hi = mid;
else
lo = mid;
}
/* invariant:
* - lo is a valid index: elt_v[lo].kv_pair reflects outcome of most recent call to BplusTree.insert()
* - .elt_v[lo].key <= key
* - if hi<.n_elt, then key < .elt_v[hi].key
*/
bool presence_flag = (key == this->lookup_elt(lo).key());
return std::make_pair(presence_flag, hi);
} /*find_lub_ix*/
template <typename Key, typename Value, typename Properties>
void
LeafNode<Key, Value, Properties>::insert_leaf_item(std::size_t ix,
std::pair<Key const, Value> const & kv_pair,
bool debug_flag) {
using xo::scope;
using xo::xtag;
scope log(XO_DEBUG(debug_flag),
xtag("self", this),
xtag("n_elt", this->n_elt()),
xtag("bf", this->branching_factor()),
xtag("ix", ix),
xtag("key", kv_pair.first),
xtag("value", kv_pair.second));
if (this->n_elt_ >= this->branching_factor()) {
assert(false);
throw std::runtime_error(tostr("LeafNode::insert_leaf: leaf already full",
xtag("leaf.n_elt", this->n_elt()),
xtag("branching_factor", this->branching_factor())));
}
std::size_t pos_ix = this->n_elt_;
while (pos_ix > ix) {
//scope x1("loop");
//x1.log(xtag("pos_ix", pos_ix));
this->lookup_elt(pos_ix) = std::move(this->lookup_elt(pos_ix - 1));
--pos_ix;
}
++(this->n_elt_);
this->lookup_elt(ix) = LeafNodeItemType(kv_pair);
log.end_scope();
} /*insert_leaf*/
template <typename Key, typename Value, typename Properties>
void
LeafNode<Key, Value, Properties>::remove_leaf(std::size_t ix, bool debug_flag) {
using xo::scope;
using xo::xtag;
scope log(XO_DEBUG(debug_flag),
xtag("self", this),
xtag("n_elt", this->n_elt()),
xtag("bf", this->branching_factor()),
xtag("ix", ix));
if (this->n_elt_ == 0) {
throw std::runtime_error(tostr("LeafNode::remove_leaf: leaf already empty",
xtag("leaf.n_elt", this->n_elt()),
xtag("branching_factor", this->branching_factor())));
}
/* TODO: removal action for position pos_ix (maintain reductions) */
std::size_t pos_ix = ix;
std::size_t end_ix = this->n_elt_ - 1;
while (pos_ix < end_ix) {
//scope x1("loop");
//x1.log(xtag("pos_ix", pos_ix));
this->lookup_elt(pos_ix) = std::move(this->lookup_elt(pos_ix + 1));
++pos_ix;
}
--(this->n_elt_);
} /*remove_leaf*/
template <typename Key, typename Value, typename Properties>
void
LeafNode<Key, Value, Properties>::prepend_from_lh_sibling(LeafNode<Key, Value, Properties> * lh, std::size_t n, bool debug_flag) {
using xo::scope;
using xo::xtag;
scope log(XO_DEBUG(debug_flag),
xtag("n", n));
if (this->n_elt() + n > this->branching_factor()) {
assert(false);
throw std::runtime_error(tostr("LeafNode.prepend_from_lh_sibling: expected combined #elt <= bf",
xtag("self.n_elt", this->n_elt()),
xtag("n", n),
xtag("bf", this->branching_factor())));
}
std::size_t n_lh = lh->n_elt();
std::size_t n_rh = this->n_elt();
/* move elts in *this to the right n steps */
for (std::size_t ixp1 = this->n_elt(); ixp1 > 0; --ixp1) {
std::size_t ix = ixp1 - 1;
this->lookup_elt(ix + n) = std::move(this->lookup_elt(ix));
}
/* xfer n elts from upper end of lh, to lower end of *this */
for (std::size_t ix = 0; ix < n; ++ix) {
this->lookup_elt(ix) = lh->lookup_elt(n_lh - n + ix);
}
this->n_elt_ += n;
lh->n_elt_ -= n;
/* note: since we didn't create/destroy any LeafNodes,
* .prev_leafnode / .next_leafnode pointers are unchanged
*/
log.end_scope();
} /*prepend_from_lh_sibling*/
template <typename Key, typename Value, typename Properties>
void
LeafNode<Key, Value, Properties>::append_from_rh_sibling(std::size_t n, LeafNode<Key, Value, Properties> * rh) {
using xo::xtag;
if (this->n_elt() + n > this->branching_factor()) {
assert(false);
throw std::runtime_error(tostr("LeafNode.append_from_rh_sibling: expected combined #elt <= bf",
xtag("self.n_elt", this->n_elt()),
xtag("n", n),
xtag("bf", this->branching_factor())));
}
std::size_t n_lh = this->n_elt();
for (std::size_t ix = 0; ix < n; ++ix) {
this->lookup_elt(n_lh + ix) = std::move(rh->lookup_elt(ix));
/* note: leaf items are key,value pairs;
* no parent pointers to fixup (cf InternalNode.append_from_rh_sibling)
*/
}
this->n_elt_ += n;
/* shuffle remaining members of rh sibling n items to the left */
for (std::size_t ix = 0; ix < rh->n_elt() - n; ++ix) {
rh->lookup_elt(ix) = std::move(rh->lookup_elt(ix + n));
}
rh->n_elt_ -= n;
/* note: since we didn't create/destroy any LeafNodes,
* .prev_leafnode / .next_leafnode pointers are unchanged
*/
} /*append_from_rh_sibling*/
template <typename Key, typename Value, typename Properties>
std::unique_ptr<LeafNode<Key, Value, Properties>>
LeafNode<Key, Value, Properties>::split_leaf_lower() {
std::size_t n_elt = this->n_elt_;
std::size_t mid_ix = n_elt / 2;
return LeafNode::annex(0, mid_ix, this);
} /*split_leaf_lower*/
template <typename Key, typename Value, typename Properties>
std::unique_ptr<LeafNode<Key, Value, Properties>>
LeafNode<Key, Value, Properties>::split_leaf_upper() {
std::size_t n_elt = this->n_elt_;
std::size_t mid_ix = n_elt / 2;
return LeafNode<Key, Value, Properties>::annex(mid_ix, n_elt, this);
} /*split_leaf_upper*/
template <typename Key, typename Value, typename Properties>
std::size_t
LeafNode<Key, Value, Properties>::verify_helper(InternalNodeType const * parent,
bool with_lub_flag,
Key const & lub_key,
LeafNodeType const * lh_leaf,
LeafNodeType const * rh_leaf) const {
using xo::xtag;
/* verify immediate parent pointer is correct */
if (this->parent() != parent) {
throw std::runtime_error(tostr("LeafNode::verify_helper"
": expected parent pointer to refer to actual parent",
xtag("stored_parent", this->parent()),
xtag("actual_parent", parent)));
}
/* verify locally stored keys appear in sorted order */
std::size_t n = this->n_elt_;
for (std::size_t i=1; i < n; ++i) {
LeafNodeItemType const & prev = this->lookup_elt(i-1);
LeafNodeItemType const & elt = this->lookup_elt(i);
if (prev.key() < elt.key()) {
;
} else {
throw std::runtime_error(tostr("LeafNode::verify_helper"
": expected local keys in strictly increasing order",
xtag("i", i),
xtag("key(i-1)", prev.key()),
xtag("key(i)", elt.key())));
}
}
if (with_lub_flag) {
if (this->lookup_elt(n-1).key() < lub_key) {
;
} else {
throw std::runtime_error(tostr("LeafNode::verify_helper"
": expected last local key before parent-supplied lub key",
xtag("n", n),
xtag("key(n-1)", this->lookup_elt(n-1).key()),
xtag("lub_key", lub_key)));
}
}
/* verify next/prev leafnode pointers are consistent */
if ((lh_leaf && (lh_leaf->next_leafnode() != this))
|| (this->prev_leafnode() != lh_leaf))
{
throw std::runtime_error(tostr("LeafNode::verify_helper"
": inconsistent prev/next leaf pointers",
xtag("parent", parent),
xtag("lh_leaf", lh_leaf),
xtag("lh_leaf.next", lh_leaf ? lh_leaf->next_leafnode() : nullptr),
xtag("self", this),
xtag("self.prev", this->prev_leafnode())));
}
if ((this->next_leafnode() != rh_leaf)
|| (rh_leaf && (rh_leaf->prev_leafnode() != this)))
{
throw std::runtime_error(tostr("LeafNode::verify_helper"
": inconsistent prev/next leaf pointers",
xtag("parent", parent),
xtag("self", this),
xtag("self.next", this->next_leafnode()),
xtag("rh_leaf", rh_leaf),
xtag("rh_leaf.prev", rh_leaf ? rh_leaf->prev_leafnode() : nullptr)));
}
return this->n_elt();
} /*verify_helper*/
template <typename Key, typename Value, typename Properties>
void
LeafNode<Key, Value, Properties>::verify_glb_key(Key const & key) const {
using xo::xtag;
LeafNodeItemType const & elt = this->lookup_elt(0);
if (elt.key() != key) {
throw std::runtime_error(tostr("LeafNode::verify_glb_key"
": expected stored greatest-lower-bound key to match leftmost leaf's key",
xtag("@", this),
xtag("reported_key", key),
xtag("actual_key", elt.key())));
}
} /*verify_glb_key*/
template <typename Key, typename Value, typename Properties>
FindNodeResult<LeafNode<Key, Value, Properties>>
LeafNode<Key, Value, Properties>::find_min_leaf_node() {
return FindNodeResult<LeafNode<Key, Value, Properties>>(0, this);
} /*find_min_leaf_node*/
template <typename Key, typename Value, typename Properties>
FindNodeResult<LeafNode<Key, Value, Properties>>
LeafNode<Key, Value, Properties>::find_max_leaf_node() {
return FindNodeResult<LeafNode<Key, Value, Properties>>(0, this);
} /*c_find_max_leaf_node*/
template <typename Key, typename Value, typename Properties>
void
LeafNode<Key, Value, Properties>::notify_remove() {
if (this->prev_leafnode_)
this->prev_leafnode_->assign_next_leafnode(this->next_leafnode_);
if (this->next_leafnode_)
this->next_leafnode_->assign_prev_leafnode(this->prev_leafnode_);
} /*notify_remove*/
template <typename Key, typename Value, typename Properties>
LeafNode<Key, Value, Properties>::LeafNode(std::size_t branching_factor)
: LeafNodeShim<Key, Value, Properties>(NodeType::leaf, branching_factor)
{
/* must call ctor explicitly for each element.
* compiler can't do this for us, b/c it doesn't know size of flexible array
*/
for (std::size_t i = 0, n = branching_factor; i < n; ++i) {
new (&(this->lookup_elt(i))) LeafNodeItemType();
}
}
template <typename Key, typename Value, typename Properties>
LeafNode<Key, Value, Properties>::LeafNode(std::pair<Key const, Value> const & kv_pair,
std::size_t branching_factor)
: LeafNodeShim<Key, Value, Properties>(NodeType::leaf, branching_factor)
{
using xo::scope;
using xo::xtag;
#ifdef NOT_USING_DEBUG
scope x("LeafNode.ctor");
#endif
this->n_elt_ = 1;
/* since .elt_v[] is a flexible array, need to invoke constructors explicitly
* (compiler doesn't know how many elements there are -> can't do it for us
*/
#ifdef NOT_USING_DEBUG
x.log(xtag("elt[0]", &(this->lookup_elt(0))));
#endif
new (&(this->lookup_elt(0))) LeafNodeItemType(kv_pair);
for (std::size_t i = 1, n = branching_factor; i < n; ++i) {
#ifdef NOT_USING_DEBUG
x.log(xtag("i", i),
xtag("elt[i]", &(this->lookup_elt(i))));
#endif
/* using placement-new to invoke ctor explicitly */
new (&(this->lookup_elt(i))) LeafNodeItemType();
}
} /*ctor*/
template <typename Key, typename Value, typename Properties>
void
LeafNode<Key, Value, Properties>::assign_siblings(LeafNode * p, LeafNode * n) {
if (p)
p->assign_next_leafnode(this);
this->prev_leafnode_ = p;
this->next_leafnode_ = n;
if (n)
n->assign_prev_leafnode(this);
} /*assign_siblings*/
} /*namespace tree*/
} /*namespace xo*/
/* end LeafNode.hpp */

View file

@ -0,0 +1,68 @@
/* @file Lhs.hpp */
#pragma once
#include <stdexcept>
namespace xo {
namespace tree {
namespace detail {
/* xo::tree::detail::BplusTreeLhsBase
*
* use for {const + non-const} versions of BplusTree::operator[]
*
* Expect: either:
* Tree = BplusTree<Key, Value, Properties>
* LeafNodeItem = Tree::LeafNodeItemType
* or
* Tree = BplusTree<Key, Value, Properties> const
* LeafNodeItem = Tree::LeafNodeItemType const
*/
template <class Tree, class LeafNodeItem>
class BplusTreeLhsBase {
public:
using mapped_type = typename Tree::mapped_type;
public:
BplusTreeLhsBase() = default;
BplusTreeLhsBase(Tree * tree, LeafNodeItem const * item)
: p_tree_{tree}, item_{item} {}
operator mapped_type const & () const {
//using xo::tostr;
if (!this->item_) {
throw std::runtime_error
("bptree: attempt to use empty lhs object as rvalue");
}
return this->item_->value();
}
protected:
Tree * p_tree_ = nullptr;
/* points to key-value pair (interior to a B+ tree LeafNode */
LeafNodeItem * item_ = nullptr;
}; /*BplusTreeLhsBase*/
/* xo::tree::detail::BplusTreeConstLhs
*
* use for const version of BplusTree::operator[]
*/
template <class BplusTree>
class BplusTreeConstLhs : public BplusTreeLhsBase<BplusTree const,
typename BplusTree::LeafNodeItemType const>
{
public:
BplusTreeConstLhs() = default;
BplusTreeConstLhs(BplusTree const * tree,
typename BplusTree::LeafNodeItemType const * item)
: BplusTreeLhsBase<BplusTree const,
typename BplusTree::LeafNodeItemType const>(tree, item) {}
}; /*BplusTreeConstLhs*/
} /*namespace detail*/
} /*namespace tree*/
} /*namespace xo*/
/* end Lhs.hpp */

View file

@ -0,0 +1,16 @@
/* @file bplustree_tags.hpp */
#pragma once
namespace xo {
namespace tree {
namespace tags {
/* ordinal_enabled: compute ordinal statistics;
* in particular maintain per-node subtree size
*/
enum ordinal_tag { ordinal_enabled, ordinal_disabled };
} /*tags*/
} /*namespace tree*/
} /*namespace xo*/
/* end bplustree_tags.hpp */

View file

@ -0,0 +1,25 @@
# ordinaltree/utest/CMakeLists.txt
# note: tests in this directory use Catch2-provided main
set(SELF_EXE utest.tree)
set(SELF_SOURCE_FILES tree_utest_main.cpp redblacktree.cpp bplustree.cpp)
add_executable(${SELF_EXE} ${SELF_SOURCE_FILES})
xo_include_options2(${SELF_EXE})
add_test(NAME ${SELF_EXE} COMMAND ${SELF_EXE})
target_code_coverage(${SELF_EXE} AUTO ALL)
# ----------------------------------------------------------------
# internal dependencies: refcnt, ...
xo_dependency(${SELF_EXE} refcnt)
xo_dependency(${SELF_EXE} indentlog)
xo_dependency(${SELF_EXE} randomgen)
# ----------------------------------------------------------------
# 3rd part dependency: catch2:
xo_external_target_dependency(${SELF_EXE} Catch2 Catch2::Catch2)
# end ordinaltree/utest/CMakeLists.txt

View file

@ -0,0 +1,814 @@
/* @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 "xo/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 */

View file

@ -0,0 +1,450 @@
/* @file random_tree_ops.hpp **/
#include "xo/randomgen/xoshiro256.hpp"
#include "xo/indentlog/scope.hpp"
#include "xo/indentlog/print/tag.hpp"
#include "xo/indentlog/print/vector.hpp"
#include "catch2/catch.hpp"
#include <algorithm>
#include <map>
#include <vector>
namespace utest {
struct Util {
/* generate vector with integers [0.. n-1] */
static std::vector<std::uint32_t> vector_upto(std::uint32_t n) {
std::vector<std::uint32_t> u(n);
for (std::uint32_t i = 0; i < n; ++i)
u[i] = i;
return u;
} /*vector_upto*/
static std::map<std::uint32_t, std::uint32_t>
map_upto(std::uint32_t n)
{
std::map<std::uint32_t, std::uint32_t> m;
for(std::uint32_t i=0; i<n; ++i) {
m[i] = i;
}
return m;
} /*map_upto*/
/* generate random permutation of integers [0.. n-1] */
static std::vector<uint32_t>
random_permutation(uint32_t n, xo::rng::xoshiro256ss *p_rgen) {
/* vector [0 .. n-1] */
std::vector<uint32_t> u = vector_upto(n);
/* shuffle to get unpredictable permutation */
std::shuffle(u.begin(), u.end(), *p_rgen);
return u;
} /*random_permutation*/
}; /*Util*/
/* note: trivial REQUIRE() call in else branch bc we still want
* catch2 to count assertions when verification succeeds
*/
# define REQUIRE_ORCAPTURE(ok_flag, catch_flag, expr) \
if (catch_flag) { \
REQUIRE((expr)); \
} else { \
REQUIRE(true); \
ok_flag &= (expr); \
}
# define REQUIRE_ORFAIL(ok_flag, catch_flag, expr) \
REQUIRE_ORCAPTURE(ok_flag, catch_flag, expr); \
if (!ok_flag) \
return ok_flag
template <typename Tree>
struct TreeUtil : public Util {
static bool
test_clear(bool catch_flag,
Tree * p_tree)
{
bool ok_flag = true;
REQUIRE_ORFAIL(ok_flag, catch_flag, p_tree->verify_ok());
p_tree->clear();
REQUIRE_ORFAIL(ok_flag, catch_flag, p_tree->verify_ok(catch_flag));
REQUIRE_ORFAIL(ok_flag, catch_flag, p_tree->empty());
REQUIRE_ORFAIL(ok_flag, catch_flag, p_tree->size() == 0);
return ok_flag;
} /*test_clear*/
/* do n random inserts (taken from *p_rgen) into *p_rbtreẹ
* inserted keys will be distinct values in [0, .., n-1]
*/
static bool
random_inserts(std::uint32_t n,
bool catch_flag,
xo::rng::xoshiro256ss * p_rgen,
Tree * p_tree)
{
using xo::xtag;
bool ok_flag = true;
xo::scope log(XO_DEBUG(catch_flag));
REQUIRE_ORFAIL(ok_flag, catch_flag, p_tree->verify_ok());
/* n keys 0..n-1 */
std::vector<std::uint32_t> u(n);
for(std::uint32_t i=0; i<n; ++i)
u[i] = i;
/* shuffle to get unpredictable insert order */
std::shuffle(u.begin(), u.end(), *p_rgen);
/* insert keys according to permutation u */
uint32_t i = 1;
for(uint32_t x : u) {
log && log(xtag("i", i), xtag("n", n), xtag("key", x));
/* .first: iterator @ insert position
* .second: true if insert occurred ( tree size incremented)
*/
auto insert_result = p_tree->insert(typename Tree::value_type(x, 10 * x));
REQUIRE_ORFAIL(ok_flag, catch_flag, p_tree->verify_ok(catch_flag));
REQUIRE_ORFAIL(ok_flag, catch_flag, insert_result.second);
/* verify: iterator returned by Treẹinsert(), refers to inserted key,value pair */
log && log(xtag("iter.node", insert_result.first.node()));
REQUIRE_ORFAIL(ok_flag, catch_flag, insert_result.first->first == x);
REQUIRE_ORFAIL(ok_flag, catch_flag, insert_result.first->second == 10 * x);
++i;
}
REQUIRE_ORFAIL(ok_flag, catch_flag, p_tree->size() == n);
return ok_flag;
} /*random_inserts*/
/* do n random removes (taken from *p_rgen) from *p_rbtree;
* assumes *p_rbtree has keys [0 .. n-1] where n=p_rbtreẹsize
*/
static bool
random_removes(bool catch_flag,
xo::rng::xoshiro256ss * p_rgen,
Tree * p_tree)
{
using xo::scope;
using xo::xtag;
bool ok_flag = true;
xo::scope log(XO_DEBUG(catch_flag));
REQUIRE_ORFAIL(ok_flag, catch_flag, p_tree->verify_ok(catch_flag));
uint32_t n = p_tree->size();
/* random permutation of keys in *p_tree */
std::vector<std::uint32_t> u
= random_permutation(n, p_rgen);
log && log(xtag("remove-order", u));
/* will keep track of which keys remain as we move them */
std::map<std::uint32_t, std::uint32_t> m = Util::map_upto(n);
/* remove keys in permutation order */
std::uint32_t i = 1;
for (std::uint32_t x : u) {
log && log("iter i: removing key from n-node tree",
xtag("i", i), xtag("key", x), xtag("n", n));
/* remove x from tracking map m also */
m.erase(x);
log && log("remove key :iter ", i, "/", n, xtag("key", x));
p_tree->erase(x);
// rbtreẹdisplay();
REQUIRE_ORFAIL(ok_flag, catch_flag, p_tree->size() == n-i);
/* amongst other things, this guarantees that keys in *p_tree
* appear in increasing order
*/
REQUIRE_ORFAIL(ok_flag, catch_flag, p_tree->verify_ok(catch_flag));
#ifdef NOT_YET
/* 1. rbtree should now contain all the keys in [0..n-1],
* with u[0]..u[i-1] excluded; this is the same as the
* contents of m.
*/
auto m_ix = m.begin();
auto m_end_ix = m.end();
auto visitor_fn =
([&m_ix, m_end_ix]
(std::pair<int, double> const & contents)
{
REQUIRE(m_ix != m_end_ix);
REQUIRE(contents.first == m_ix->second);
++m_ix;
});
p_tree->visit_inorder(visitor_fn);
#endif
++i;
}
REQUIRE_ORFAIL(ok_flag, catch_flag, m.empty());
REQUIRE_ORFAIL(ok_flag, catch_flag, p_tree->size() == 0);
log.end_scope();
return ok_flag;
} /*random_removes*/
/* Require:
* - tree has keys [0..n-1], where n=treẹsize()
* - for each key k, associated value is 10*k
*/
static bool
random_lookups(bool catch_flag,
Tree const & tree,
xo::rng::xoshiro256ss * p_rgen)
{
using xo::scope;
using xo::xtag;
xo::scope log(XO_DEBUG(catch_flag));
/* -> false if/when verification fails */
bool ok_flag = true;
REQUIRE_ORFAIL(ok_flag, catch_flag, tree.verify_ok(catch_flag));
size_t n = tree.size();
std::vector<std::uint32_t> u
= random_permutation(n, p_rgen);
/* lookup keys in permutation order */
std::uint32_t i = 1;
for (std::uint32_t x : u) {
INFO(tostr(xtag("i", i), xtag("n", n), xtag("x", x)));
REQUIRE_ORFAIL(ok_flag, catch_flag, tree[x] == x*10);
REQUIRE_ORFAIL(ok_flag, catch_flag, tree.verify_ok(catch_flag));
REQUIRE_ORFAIL(ok_flag, catch_flag, tree.size() == n);
/* also test treẹfind() */
auto find_ix = tree.find(x);
REQUIRE_ORFAIL(ok_flag, catch_flag, find_ix != tree.end());
REQUIRE_ORFAIL(ok_flag, catch_flag, find_ix->first == x);
REQUIRE_ORFAIL(ok_flag, catch_flag, find_ix->second == x*10);
++i;
}
REQUIRE_ORFAIL(ok_flag, catch_flag, tree.size() == n);
log.end_scope();
return ok_flag;
} /*random_lookups*/
/* Require:
* - tree has keys [0..n-1], where n=treẹsize()
* - tree value at key k is dvalue+10*k
*/
static bool
check_ordinal_lookup(std::uint32_t dvalue,
bool catch_flag,
Tree const & tree)
{
using xo::scope;
using xo::xtag;
/* -> false if/when verification fails */
bool ok_flag = true;
xo::scope log(XO_DEBUG(catch_flag));
std::size_t const n = tree.size();
std::size_t i = 0;
log && log("tree with size n", xtag("n", n));
for (std::size_t i=0; i<n; ++i) {
typename Tree::const_iterator ix;
try {
ix = tree.find_ith(i); /* find_ith() may throw if broken */
} catch(...) {
if (catch_flag)
throw;
}
REQUIRE_ORFAIL(ok_flag, catch_flag, ix.is_dereferenceable());
REQUIRE_ORFAIL(ok_flag, catch_flag, (ix != tree.end()));
REQUIRE_ORFAIL(ok_flag, catch_flag, (ix->first == i));
REQUIRE_ORFAIL(ok_flag, catch_flag, (ix->second == 10*i + dvalue));
}
log.end_scope();
return ok_flag;
} /*check_ordinal_lookup*/
/* Require:
* - tree has keys [0..n-1], where n=treẹsize()
* - tree values at key k is dvalue+10*k
*
* catch_flag. true -> log to console + interact with catch2
* false -> verify iteration behavior for return code
*/
static bool
check_bidirectional_iterator(uint32_t dvalue,
bool catch_flag,
Tree const & tree)
{
using xo::scope;
using xo::xtag;
/* -> false if/when verification fails */
bool ok_flag = true;
std::size_t const n = tree.size();
xo::scope log(XO_DEBUG(catch_flag));
log && log("tree with size n", xtag("n", n));
{
std::size_t i = 0;
auto end_ix = tree.end();
log && log(xtag("end_ix", end_ix));
auto begin_ix = tree.begin();
auto ix = begin_ix;
int last_key = -1;
while (ix != end_ix) {
log && log("forward loop top",
xtag("i", i),
xtag("ix", ix));
REQUIRE_ORFAIL(ok_flag, catch_flag, ix->first == i);
REQUIRE_ORFAIL(ok_flag, catch_flag, ix->second == dvalue + 10*i);
if(i > 0) {
REQUIRE_ORFAIL(ok_flag, catch_flag, ix->first > last_key);
}
last_key = ix->first;
++i;
++ix;
log && log("forward loop bottom",
xtag("last_key", last_key),
xtag("next ix", ix));
}
/* should have visited exactly n locations */
REQUIRE_ORFAIL(ok_flag, catch_flag, i == n);
REQUIRE_ORFAIL(ok_flag, catch_flag, ix == end_ix);
log && log(xtag("ix", ix), xtag("begin_ix", begin_ix));
/* now run iterator backwards,
* starting from "one past the end"
*/
if(ix != begin_ix) {
do {
--i;
--ix;
log && log("forward backup",
xtag("i", i),
xtag("ix", ix));
REQUIRE_ORFAIL(ok_flag, catch_flag, ix.is_dereferenceable());
log && log(xtag("ix.first", (*ix).first));
REQUIRE_ORFAIL(ok_flag, catch_flag, (*ix).first == i);
} while (ix != begin_ix);
}
/* should have visited exactly n locations in reverse */
REQUIRE_ORFAIL(ok_flag, catch_flag, i == 0);
}
/* ----- reverse iterators ----- */
{
std::int64_t i = n - 1;
auto rbegin_ix = tree.rbegin();
auto rend_ix = tree.rend();
auto rix = rbegin_ix;
int last_key = -1;
while (rix != rend_ix) {
log && log("reverse loop top",
xtag("i", i),
xtag("rix", rix));
REQUIRE_ORFAIL(ok_flag, catch_flag, rix->first == i);
REQUIRE_ORFAIL(ok_flag, catch_flag, rix->second == dvalue + 10*i);
if (i < n-1) {
REQUIRE_ORFAIL(ok_flag, catch_flag, rix->first < last_key);
}
last_key = rix->first;
--i;
++rix;
log && log("reverse loop bottom",
xtag("last_key", last_key),
xtag("next ix", rix));
}
/* should have visited exactly n locations */
REQUIRE_ORFAIL(ok_flag, catch_flag, i == -1);
log && log(xtag("rbegin_ix", rbegin_ix));
/* now run reverse iterator backwrds,
* starting from "one before the beginning"
*/
if (rix != rbegin_ix) {
do {
++i;
--rix;
log && log("reverse backup",
xtag("i", i),
xtag("rix", rix),
xtag("rix.first", rix->first));
REQUIRE_ORFAIL(ok_flag, catch_flag, (*rix).first == i);
} while (rix != rbegin_ix);
}
/* should have visited exactly n locations in reversê2 */
REQUIRE_ORFAIL(ok_flag, catch_flag, i == n - 1);
}
log.end_scope();
return ok_flag;
} /*check_bidirectional_iterator*/
}; /*TreeUtil*/
} /*namespace utest*/
/* end random_tree_ops.hpp */

View file

@ -0,0 +1,248 @@
/* @file redblacktree.cpp */
#include "random_tree_ops.hpp"
#include "xo/ordinaltree/RedBlackTree.hpp"
#include <map>
namespace {
using xo::tree::RedBlackTree;
using xo::tree::SumReduce;
using xo::tree::OrdinalReduce;
using xo::tree::NullReduce;
using xo::rng::xoshiro256ss;
using utest::Util;
using utest::TreeUtil;
using xo::scope;
using xo::scope_setup;
using xo::xtag;
//using RbTree = RedBlackTree<int, double, OrdinalReduce<double>>;
using RbTree = RedBlackTree<int, double, SumReduce<double>>;
#ifdef OBSOLETE
/* Require:
* - rbtree has keys [0..n-1] where n=rbtree.size(),
* - rbtree value at key k is dvalue+10*k
*/
void
check_ordinal_lookup(uint32_t dvalue,
RbTree const & rbtree)
{
size_t const n = rbtree.size();
size_t i = 0;
for(size_t i=0; i<n; ++i) {
RbTree::const_iterator ix = rbtree.find_ith(i);
REQUIRE(ix != rbtree.end());
REQUIRE(ix->first == i);
}
} /*check_ordinal_lookup*/
#endif
/* check that RedBlackTree<>::find_sum_glb() works as advertised.
*
* partial sums of v[j] for j<=i will be:
*
* (i+1) . i
* 10 . --------- + ((i+1) . dvalue)
* 2
*
* = (i+1).(5.i + dvalue)
*
* Require:
* - rbtree has keys [0..n-1], where n=rbtree.size()
* - rbtree value at key k is dvalue+10*k
*/
void
check_reduced_sum(uint32_t dvalue,
RbTree const & rbtree)
{
size_t const n = rbtree.size();
for(size_t i = 0; i < n; ++i) {
/* compute reduction up to key=i */
double reduced_upto
= rbtree.reduce_lub(i /*key*/,
true /*is_closed*/);
double reduced = (i+1) * (5*i + dvalue);
INFO(tostr(xtag("i", i), xtag("n", n),
xtag("tree.reduced_upto", reduced_upto),
xtag("reduced", reduced),
xtag("dvalue", dvalue)));
auto glb_ix = rbtree.cfind_sum_glb(reduced);
REQUIRE(reduced_upto == reduced);
REQUIRE(glb_ix.is_dereferenceable());
/* glb_ix is truth-y */
REQUIRE(glb_ix);
REQUIRE(glb_ix->first == i);
}
} /*check_reduced_sum*/
#ifdef OBSOLETE
/* Require:
* - *p_rbtree has keys [0..n-1], where n=rbtree.size()
* - for each key k, associated value is 10*k
*/
void
random_lookups(RbTree const & rbtree,
xoshiro256ss * p_rgen)
{
REQUIRE(rbtree.verify_ok());
size_t n = rbtree.size();
std::vector<uint32_t> u
= Util::random_permutation(n, p_rgen);
/* lookup keys in permutation order */
uint32_t i = 1;
for (uint32_t x : u) {
INFO(tostr(xtag("i", i), xtag("n", n), xtag("x", x)));
REQUIRE(rbtree[x] == x*10);
REQUIRE(rbtree.verify_ok());
REQUIRE(rbtree.size() == n);
++i;
}
REQUIRE(rbtree.size() == n);
} /*random_lookups*/
#endif
/* Require:
* - *p_rbtree has keys [0..n-1], where n=rbtree.size()
* - for each key k, associated value is 10*k
*
* Promise:
* - for each key k, associated value is dvalue + 10*k
*/
void
random_updates(uint32_t dvalue,
RbTree * p_rbtree,
xoshiro256ss * p_rgen)
{
REQUIRE(p_rbtree->verify_ok());
std::size_t n = p_rbtree->size();
std::vector<uint32_t> u
= Util::random_permutation(n, p_rgen);
/* update key/value pairs in permutation order */
uint32_t i = 1;
for (uint32_t x : u) {
REQUIRE((*p_rbtree)[x] == x*10);
(*p_rbtree)[x] = dvalue + 10*x;
REQUIRE((*p_rbtree)[x] == dvalue + 10*x);
REQUIRE(p_rbtree->verify_ok());
/* assignment to existing key does not change tree size */
REQUIRE(p_rbtree->size() == n);
++i;
}
REQUIRE(p_rbtree->size() == n);
} /*random_updates_1*/
TEST_CASE("rbtree", "[redblacktree]") {
RbTree rbtree;
std::uint64_t seed = 14950349842636922572UL;
/* can reseed from /dev/urandom with: */
//arc4random_buf(&seed, sizeof(seed));
auto rgen = xo::rng::xoshiro256ss(seed);
/* perform a series of tests with increasing scale */
for(std::uint32_t n=0; n<=1024; ) {
bool ok_flag = false;
for (std::uint32_t attention = 0; !ok_flag && (attention < 2); ++attention) {
/* attention=0:
* - no logging
* - detect assertion failures, but don't report them to catch2
* attention=1:
* - only runs if failure detected with attention=0
* - full logging
* - report to catch
*/
bool debug_flag = (attention == 1);
scope log(XO_DEBUG2(debug_flag, "rbtree"));
log && log(xtag("size", n));
ok_flag = true;
if (n == 0) {
/* check iteration on empty tree */
ok_flag &= TreeUtil<RbTree>::check_bidirectional_iterator(0 /*dvalue - not used*/,
debug_flag,
rbtree);
} else {
/* insert [0..n-1] in random order */
ok_flag &= TreeUtil<RbTree>::random_inserts(n, debug_flag, &rgen, &rbtree);
/* TODO: generalize remaining helpers; share with bplustree unit test */
/* check iterator traverses [0..n-1] in both directions (using ++ and --) */
ok_flag &= TreeUtil<RbTree>::check_ordinal_lookup(0 /*dvalue*/,
debug_flag,
rbtree);
/* verify end-to-end iteration */
ok_flag &= TreeUtil<RbTree>::check_bidirectional_iterator(0,
debug_flag,
rbtree);
/* verify behavior of .reduce_lub(), .find_sum_glb() */
check_reduced_sum(0, rbtree);
/* verify behavior of read-only variant of operator[] */
ok_flag &= TreeUtil<RbTree>::random_lookups(debug_flag,
rbtree,
&rgen);
/* verify that lookups didn't somehow disturb tree contents */
ok_flag &= TreeUtil<RbTree>::check_ordinal_lookup(0 /*dvalue*/,
debug_flag,
rbtree);
ok_flag &= TreeUtil<RbTree>::check_bidirectional_iterator(0,
debug_flag,
rbtree);
/* verify update via read/write operator[] */
random_updates(10000, &rbtree, &rgen);
/* verify that updates changed tree contents in expected way */
ok_flag &= TreeUtil<RbTree>::check_ordinal_lookup(10000 /*dvalue*/,
debug_flag,
rbtree);
/* verify end-to-end iteration */
ok_flag &= TreeUtil<RbTree>::check_bidirectional_iterator(10000,
debug_flag,
rbtree);
/* verify behavior of .reduce_lub(), .find_sum_glb() */
check_reduced_sum(10000, rbtree);
/* verify behavior of read/write variant of operator[] */
ok_flag &= TreeUtil<RbTree>::random_removes(debug_flag, &rgen, &rbtree);
}
log.end_scope();
}
if (n == 0)
n = 1;
else
n = 2*n;
}
} /*TEST_CASE(rbtree)*/
} /*namespace*/
/* end redblacktree.cpp */

View file

@ -0,0 +1,7 @@
/* @file tree_utest_main.cpp */
#define CATCH_CONFIG_MAIN
#define CATCH_CONFIG_ENABLE_BENCHMARKING
#include "catch2/catch.hpp"
/* end tree_utest_main.cpp */