From 8fe1b8d7fc9fdf9b80aa2a1f12c4ff9bec103fa4 Mon Sep 17 00:00:00 2001 From: jbajic Date: Thu, 24 Nov 2022 17:44:41 +0100 Subject: [PATCH 01/16] Add std::map and skiplist benchamrk --- tests/benchmark/CMakeLists.txt | 11 +- tests/benchmark/data_structures.cpp | 169 +++++++++++++++++++++++++++ tests/benchmark/skip_list_common.hpp | 26 ++++- 3 files changed, 202 insertions(+), 4 deletions(-) create mode 100644 tests/benchmark/data_structures.cpp diff --git a/tests/benchmark/CMakeLists.txt b/tests/benchmark/CMakeLists.txt index 31f0eebc0..46905a4d8 100644 --- a/tests/benchmark/CMakeLists.txt +++ b/tests/benchmark/CMakeLists.txt @@ -9,11 +9,13 @@ function(add_benchmark test_cpp) get_filename_component(exec_name ${test_cpp} NAME_WE) set(target_name ${test_prefix}${exec_name}) add_executable(${target_name} ${test_cpp} ${ARGN}) + # OUTPUT_NAME sets the real name of a target when it is built and can be # used to help create two targets of the same name even though CMake # requires unique logical target names set_target_properties(${target_name} PROPERTIES OUTPUT_NAME ${exec_name}) target_link_libraries(${target_name} benchmark gflags) + # register test add_test(${target_name} ${exec_name}) add_dependencies(memgraph__benchmark ${target_name}) @@ -37,9 +39,9 @@ target_link_libraries(${test_prefix}profile mg-query) add_benchmark(query/stripped.cpp) target_link_libraries(${test_prefix}stripped mg-query) -if (MG_ENTERPRISE) -add_benchmark(rpc.cpp) -target_link_libraries(${test_prefix}rpc mg-rpc) +if(MG_ENTERPRISE) + add_benchmark(rpc.cpp) + target_link_libraries(${test_prefix}rpc mg-rpc) endif() add_benchmark(skip_list_random.cpp) @@ -65,3 +67,6 @@ target_link_libraries(${test_prefix}storage_v2_property_store mg-storage-v2) add_benchmark(future.cpp) target_link_libraries(${test_prefix}future mg-io) + +add_benchmark(data_structures.cpp) +target_link_libraries(${test_prefix}data_structures mg-utils) diff --git a/tests/benchmark/data_structures.cpp b/tests/benchmark/data_structures.cpp new file mode 100644 index 000000000..f130d6faf --- /dev/null +++ b/tests/benchmark/data_structures.cpp @@ -0,0 +1,169 @@ +// Copyright 2022 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "btree_map.hpp" +#include "skip_list_common.hpp" +#include "utils/skip_list.hpp" + +DEFINE_int32(max_element, 20000, "Maximum element in the intial list"); +DEFINE_int32(max_range, 2000000, "Maximum range used for the test"); +DEFINE_string(ds, "skip_list", "Which DS to test"); + +template +concept BasicIndexStructure = requires(ContainerType a, const ContainerType b) { + { a.contains() } -> std::same_as; + { a.find() } -> std::same_as; + { a.insert() } -> std::same_as>; + { a.remove() } -> std::same_as; +}; + +template +class BppStructure final { + using value_type = typename tlx::btree_map::value_type; + using iterator = typename tlx::btree_map::iterator; + + public: + bool contains(const T key) const { return data.exists(key); } + + auto find(const T key) const { return data.find(key); } + + std::pair insert(T val) { return data.insert({val, val}); } + + bool remove(const T key) { return data.erase(key); } + + size_t size() const { return data.size(); }; + + iterator end() { return data.end(); } + + private: + tlx::btree_map data; +}; + +template +class MapStructure final { + using value_type = typename std::map::value_type; + using iterator = typename std::map::iterator; + + public: + bool contains(const T key) const { return data.contains(key); } + + auto find(const T key) const { return data.find(key); } + + std::pair insert(T val) { return data.insert({val, val}); } + + bool remove(const T key) { return data.erase(key); } + + size_t size() const { return data.size(); }; + + iterator end() { return data.end(); } + + private: + std::map data; +}; + +template +void RunDSTest(DataStructure &ds) { + RunTest([&ds](const std::atomic &run, auto &stats) { + std::mt19937 generator(std::random_device{}()); + std::uniform_int_distribution distribution(0, 3); + std::mt19937 i_generator(std::random_device{}()); + std::uniform_int_distribution i_distribution(0, FLAGS_max_range); + + while (run.load(std::memory_order_relaxed)) { + auto value = distribution(generator); + + auto item = i_distribution(i_generator); + switch (value) { + case 0: + stats.succ[OP_INSERT] += static_cast(ds.insert(item).second); + break; + case 1: + stats.succ[OP_CONTAINS] += static_cast(ds.contains(item)); + break; + case 2: + stats.succ[OP_REMOVE] += static_cast(ds.remove(item)); + break; + case 3: + stats.succ[OP_FIND] += static_cast(ds.find(item) != ds.end()); + break; + default: + std::terminate(); + } + ++stats.total; + } + }); +} + +template +void GenerateData(Accessor &acc) { + for (uint64_t i = 0; i <= FLAGS_max_element; ++i) { + MG_ASSERT(acc.insert(i).second); + } +} + +template +void AsserData(Accessor &acc) { + if constexpr (std::is_same_v>) { + uint64_t val = 0; + for (auto item : acc) { + MG_ASSERT(item == val); + ++val; + } + MG_ASSERT(val == FLAGS_max_element + 1); + } else { + MG_ASSERT(acc.size() == FLAGS_max_element + 1); + } +} + +int main(int argc, char **argv) { + gflags::ParseCommandLineFlags(&argc, &argv, true); + + if (FLAGS_ds == "skip_list") { + std::cout << "#### Testing skip list" << std::endl; + memgraph::utils::SkipList list; + auto acc = list.access(); + + GenerateData(acc); + AsserData(acc); + + RunDSTest(acc); + } else if (FLAGS_ds == "map") { + std::cout << "#### Testing map" << std::endl; + MapStructure map; + + GenerateData(map); + AsserData(map); + + RunDSTest(map); + } else if (FLAGS_ds == "bpp") { + std::cout << "#### Testing B+ tree" << std::endl; + BppStructure bpp; + + GenerateData(bpp); + AsserData(bpp); + + RunDSTest(bpp); + } else { + throw std::runtime_error("Select an existing data structure!"); + } + + return 0; +} diff --git a/tests/benchmark/skip_list_common.hpp b/tests/benchmark/skip_list_common.hpp index 988a2dfb7..8dd4c705b 100644 --- a/tests/benchmark/skip_list_common.hpp +++ b/tests/benchmark/skip_list_common.hpp @@ -1,4 +1,4 @@ -// Copyright 2021 Memgraph Ltd. +// Copyright 2022 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -81,3 +81,27 @@ inline void RunConcurrentTest(std::function *, Stats *)> std::cout << "Total successful: " << tot << " (" << tot / FLAGS_duration << " calls/s)" << std::endl; std::cout << "Total ops: " << tops << " (" << tops / FLAGS_duration << " calls/s)" << std::endl; } + +inline void RunTest(std::function &, Stats &)> test_func) { + Stats stats; + std::atomic run{true}; + + { + std::jthread bg_thread(test_func, std::cref(run), std::ref(stats)); + std::this_thread::sleep_for(std::chrono::seconds(FLAGS_duration)); + run.store(false, std::memory_order_relaxed); + } + + std::cout << " Operations: " << stats.total << std::endl; + std::cout << " Successful insert: " << stats.succ[0] << std::endl; + std::cout << " Successful contains: " << stats.succ[1] << std::endl; + std::cout << " Successful remove: " << stats.succ[2] << std::endl; + std::cout << " Successful find: " << stats.succ[3] << std::endl; + std::cout << std::endl; + + const auto tot = stats.succ[0] + stats.succ[1] + stats.succ[2] + stats.succ[3]; + const auto tops = stats.total; + + std::cout << "Total successful: " << tot << " (" << tot / FLAGS_duration << " calls/s)" << std::endl; + std::cout << "Total ops: " << tops << " (" << tops / FLAGS_duration << " calls/s)" << std::endl; +} From c5138c8d58d68580484646ba91acae41e2d7f6d4 Mon Sep 17 00:00:00 2001 From: jbajic Date: Thu, 24 Nov 2022 17:45:43 +0100 Subject: [PATCH 02/16] Add b+ tree --- src/utils/skip_list.hpp | 2 + tests/benchmark/btree.hpp | 3134 +++++++++++++++++++++++++++++++++ tests/benchmark/btree_map.hpp | 475 +++++ tests/benchmark/core.hpp | 267 +++ 4 files changed, 3878 insertions(+) create mode 100644 tests/benchmark/btree.hpp create mode 100644 tests/benchmark/btree_map.hpp create mode 100644 tests/benchmark/core.hpp diff --git a/src/utils/skip_list.hpp b/src/utils/skip_list.hpp index 486aade57..7d4897884 100644 --- a/src/utils/skip_list.hpp +++ b/src/utils/skip_list.hpp @@ -621,6 +621,8 @@ class SkipList final { TNode *node_; }; + using iterator = Iterator; + class Accessor final { private: friend class SkipList; diff --git a/tests/benchmark/btree.hpp b/tests/benchmark/btree.hpp new file mode 100644 index 000000000..97b912fb0 --- /dev/null +++ b/tests/benchmark/btree.hpp @@ -0,0 +1,3134 @@ +/******************************************************************************* + * tlx/container/btree.hpp + * + * Part of tlx - http://panthema.net/tlx + * + * Copyright (C) 2008-2017 Timo Bingmann + * + * All rights reserved. Published under the Boost Software License, Version 1.0 + ******************************************************************************/ + +#ifndef TLX_CONTAINER_BTREE_HEADER +#define TLX_CONTAINER_BTREE_HEADER + +#include "core.hpp" + +// *** Required Headers from the STL + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace tlx { + +//! \addtogroup tlx_container +//! \{ +//! \defgroup tlx_container_btree B+ Trees +//! B+ tree variants +//! \{ + +// *** Debugging Macros + +#ifdef TLX_BTREE_DEBUG + +#include + +//! Print out debug information to std::cout if TLX_BTREE_DEBUG is defined. +#define TLX_BTREE_PRINT(x) \ + do { \ + if (debug) (std::cout << x << std::endl); \ + } while (0) + +//! Assertion only if TLX_BTREE_DEBUG is defined. This is not used in verify(). +#define TLX_BTREE_ASSERT(x) \ + do { \ + assert(x); \ + } while (0) + +#else + +//! Print out debug information to std::cout if TLX_BTREE_DEBUG is defined. +#define TLX_BTREE_PRINT(x) \ + do { \ + } while (0) + +//! Assertion only if TLX_BTREE_DEBUG is defined. This is not used in verify(). +#define TLX_BTREE_ASSERT(x) \ + do { \ + } while (0) + +#endif + +//! The maximum of a and b. Used in some compile-time formulas. +#define TLX_BTREE_MAX(a, b) ((a) < (b) ? (b) : (a)) + +#ifndef TLX_BTREE_FRIENDS +//! The macro TLX_BTREE_FRIENDS can be used by outside class to access the B+ +//! tree internals. This was added for wxBTreeDemo to be able to draw the +//! tree. +#define TLX_BTREE_FRIENDS friend class btree_friend +#endif + +/*! + * Generates default traits for a B+ tree used as a set or map. It estimates + * leaf and inner node sizes by assuming a cache line multiple of 256 bytes. + */ +template +struct btree_default_traits { + //! If true, the tree will self verify its invariants after each insert() or + //! erase(). The header must have been compiled with TLX_BTREE_DEBUG + //! defined. + static const bool self_verify = false; + + //! If true, the tree will print out debug information and a tree dump + //! during insert() or erase() operation. The header must have been + //! compiled with TLX_BTREE_DEBUG defined and key_type must be std::ostream + //! printable. + static const bool debug = false; + + //! Number of slots in each leaf of the tree. Estimated so that each node + //! has a size of about 256 bytes. + static const int leaf_slots = TLX_BTREE_MAX(8, 256 / (sizeof(Value))); + + //! Number of slots in each inner node of the tree. Estimated so that each + //! node has a size of about 256 bytes. + static const int inner_slots = TLX_BTREE_MAX(8, 256 / (sizeof(Key) + sizeof(void *))); + + //! As of stx-btree-0.9, the code does linear search in find_lower() and + //! find_upper() instead of binary_search, unless the node size is larger + //! than this threshold. See notes at + //! http://panthema.net/2013/0504-STX-B+Tree-Binary-vs-Linear-Search + static const size_t binsearch_threshold = 256; +}; + +/*! + * Basic class implementing a B+ tree data structure in memory. + * + * The base implementation of an in-memory B+ tree. It is based on the + * implementation in Cormen's Introduction into Algorithms, Jan Jannink's paper + * and other algorithm resources. Almost all STL-required function calls are + * implemented. The asymptotic time requirements of the STL are not always + * fulfilled in theory, however, in practice this B+ tree performs better than a + * red-black tree and almost always uses less memory. The insertion function + * splits the nodes on the recursion unroll. Erase is largely based on Jannink's + * ideas. + * + * This class is specialized into btree_set, btree_multiset, btree_map and + * btree_multimap using default template parameters and facade functions. + */ +template , + typename Traits = btree_default_traits, bool Duplicates = false, + typename Allocator = std::allocator> +class BTree { + public: + //! \name Template Parameter Types + //! \{ + + //! First template parameter: The key type of the B+ tree. This is stored in + //! inner nodes. + typedef Key key_type; + + //! Second template parameter: Composition pair of key and data types, or + //! just the key for set containers. This data type is stored in the leaves. + typedef Value value_type; + + //! Third template: key extractor class to pull key_type from value_type. + typedef KeyOfValue key_of_value; + + //! Fourth template parameter: key_type comparison function object + typedef Compare key_compare; + + //! Fifth template parameter: Traits object used to define more parameters + //! of the B+ tree + typedef Traits traits; + + //! Sixth template parameter: Allow duplicate keys in the B+ tree. Used to + //! implement multiset and multimap. + static const bool allow_duplicates = Duplicates; + + //! Seventh template parameter: STL allocator for tree nodes + typedef Allocator allocator_type; + + //! \} + + // The macro TLX_BTREE_FRIENDS can be used by outside class to access the B+ + // tree internals. This was added for wxBTreeDemo to be able to draw the + // tree. + TLX_BTREE_FRIENDS; + + public: + //! \name Constructed Types + //! \{ + + //! Typedef of our own type + typedef BTree Self; + + //! Size type used to count keys + typedef size_t size_type; + + //! \} + + public: + //! \name Static Constant Options and Values of the B+ Tree + //! \{ + + //! Base B+ tree parameter: The number of key/data slots in each leaf + static const unsigned short leaf_slotmax = traits::leaf_slots; + + //! Base B+ tree parameter: The number of key slots in each inner node, + //! this can differ from slots in each leaf. + static const unsigned short inner_slotmax = traits::inner_slots; + + //! Computed B+ tree parameter: The minimum number of key/data slots used + //! in a leaf. If fewer slots are used, the leaf will be merged or slots + //! shifted from it's siblings. + static const unsigned short leaf_slotmin = (leaf_slotmax / 2); + + //! Computed B+ tree parameter: The minimum number of key slots used + //! in an inner node. If fewer slots are used, the inner node will be + //! merged or slots shifted from it's siblings. + static const unsigned short inner_slotmin = (inner_slotmax / 2); + + //! Debug parameter: Enables expensive and thorough checking of the B+ tree + //! invariants after each insert/erase operation. + static const bool self_verify = traits::self_verify; + + //! Debug parameter: Prints out lots of debug information about how the + //! algorithms change the tree. Requires the header file to be compiled + //! with TLX_BTREE_DEBUG and the key type must be std::ostream printable. + static const bool debug = traits::debug; + + //! \} + + private: + //! \name Node Classes for In-Memory Nodes + //! \{ + + //! The header structure of each node in-memory. This structure is extended + //! by InnerNode or LeafNode. + struct node { + //! Level in the b-tree, if level == 0 -> leaf node + unsigned short level; + + //! Number of key slotuse use, so the number of valid children or data + //! pointers + unsigned short slotuse; + + //! Delayed initialisation of constructed node. + void initialize(const unsigned short l) { + level = l; + slotuse = 0; + } + + //! True if this is a leaf node. + bool is_leafnode() const { return (level == 0); } + }; + + //! Extended structure of a inner node in-memory. Contains only keys and no + //! data items. + struct InnerNode : public node { + //! Define an related allocator for the InnerNode structs. + typedef typename std::allocator_traits::template rebind_alloc alloc_type; + + //! Keys of children or data pointers + key_type slotkey[inner_slotmax]; // NOLINT + + //! Pointers to children + node *childid[inner_slotmax + 1]; // NOLINT + + //! Set variables to initial values. + void initialize(const unsigned short l) { node::initialize(l); } + + //! Return key in slot s + const key_type &key(size_t s) const { return slotkey[s]; } + + //! True if the node's slots are full. + bool is_full() const { return (node::slotuse == inner_slotmax); } + + //! True if few used entries, less than half full. + bool is_few() const { return (node::slotuse <= inner_slotmin); } + + //! True if node has too few entries. + bool is_underflow() const { return (node::slotuse < inner_slotmin); } + }; + + //! Extended structure of a leaf node in memory. Contains pairs of keys and + //! data items. Key and data slots are kept together in value_type. + struct LeafNode : public node { + //! Define an related allocator for the LeafNode structs. + typedef typename std::allocator_traits::template rebind_alloc alloc_type; + + //! Double linked list pointers to traverse the leaves + LeafNode *prev_leaf; + + //! Double linked list pointers to traverse the leaves + LeafNode *next_leaf; + + //! Array of (key, data) pairs + value_type slotdata[leaf_slotmax]; // NOLINT + + //! Set variables to initial values + void initialize() { + node::initialize(0); + prev_leaf = next_leaf = nullptr; + } + + //! Return key in slot s. + const key_type &key(size_t s) const { return key_of_value::get(slotdata[s]); } + + //! True if the node's slots are full. + bool is_full() const { return (node::slotuse == leaf_slotmax); } + + //! True if few used entries, less than half full. + bool is_few() const { return (node::slotuse <= leaf_slotmin); } + + //! True if node has too few entries. + bool is_underflow() const { return (node::slotuse < leaf_slotmin); } + + //! Set the (key,data) pair in slot. Overloaded function used by + //! bulk_load(). + void set_slot(unsigned short slot, const value_type &value) { + TLX_BTREE_ASSERT(slot < node::slotuse); + slotdata[slot] = value; + } + }; + + //! \} + + public: + //! \name Iterators and Reverse Iterators + //! \{ + + class iterator; + class const_iterator; + class reverse_iterator; + class const_reverse_iterator; + + //! STL-like iterator object for B+ tree items. The iterator points to a + //! specific slot number in a leaf. + class iterator { + public: + // *** Types + + //! The key type of the btree. Returned by key(). + typedef typename BTree::key_type key_type; + + //! The value type of the btree. Returned by operator*(). + typedef typename BTree::value_type value_type; + + //! Reference to the value_type. STL required. + typedef value_type &reference; + + //! Pointer to the value_type. STL required. + typedef value_type *pointer; + + //! STL-magic iterator category + typedef std::bidirectional_iterator_tag iterator_category; + + //! STL-magic + typedef ptrdiff_t difference_type; + + //! Our own type + typedef iterator self; + + private: + // *** Members + + //! The currently referenced leaf node of the tree + typename BTree::LeafNode *curr_leaf; + + //! Current key/data slot referenced + unsigned short curr_slot; + + //! Friendly to the const_iterator, so it may access the two data items + //! directly. + friend class const_iterator; + + //! Also friendly to the reverse_iterator, so it may access the two + //! data items directly. + friend class reverse_iterator; + + //! Also friendly to the const_reverse_iterator, so it may access the + //! two data items directly. + friend class const_reverse_iterator; + + //! Also friendly to the base btree class, because erase_iter() needs + //! to read the curr_leaf and curr_slot values directly. + friend class BTree; + + // The macro TLX_BTREE_FRIENDS can be used by outside class to access + // the B+ tree internals. This was added for wxBTreeDemo to be able to + // draw the tree. + TLX_BTREE_FRIENDS; + + public: + // *** Methods + + //! Default-Constructor of a mutable iterator + iterator() : curr_leaf(nullptr), curr_slot(0) {} + + //! Initializing-Constructor of a mutable iterator + iterator(typename BTree::LeafNode *l, unsigned short s) : curr_leaf(l), curr_slot(s) {} + + //! Copy-constructor from a reverse iterator + iterator(const reverse_iterator &it) // NOLINT + : curr_leaf(it.curr_leaf), curr_slot(it.curr_slot) {} + + //! Dereference the iterator. + reference operator*() const { return curr_leaf->slotdata[curr_slot]; } + + //! Dereference the iterator. + pointer operator->() const { return &curr_leaf->slotdata[curr_slot]; } + + //! Key of the current slot. + const key_type &key() const { return curr_leaf->key(curr_slot); } + + //! Prefix++ advance the iterator to the next slot. + iterator &operator++() { + if (curr_slot + 1u < curr_leaf->slotuse) { + ++curr_slot; + } else if (curr_leaf->next_leaf != nullptr) { + curr_leaf = curr_leaf->next_leaf; + curr_slot = 0; + } else { + // this is end() + curr_slot = curr_leaf->slotuse; + } + + return *this; + } + + //! Postfix++ advance the iterator to the next slot. + iterator operator++(int) { + iterator tmp = *this; // copy ourselves + + if (curr_slot + 1u < curr_leaf->slotuse) { + ++curr_slot; + } else if (curr_leaf->next_leaf != nullptr) { + curr_leaf = curr_leaf->next_leaf; + curr_slot = 0; + } else { + // this is end() + curr_slot = curr_leaf->slotuse; + } + + return tmp; + } + + //! Prefix-- backstep the iterator to the last slot. + iterator &operator--() { + if (curr_slot > 0) { + --curr_slot; + } else if (curr_leaf->prev_leaf != nullptr) { + curr_leaf = curr_leaf->prev_leaf; + curr_slot = curr_leaf->slotuse - 1; + } else { + // this is begin() + curr_slot = 0; + } + + return *this; + } + + //! Postfix-- backstep the iterator to the last slot. + iterator operator--(int) { + iterator tmp = *this; // copy ourselves + + if (curr_slot > 0) { + --curr_slot; + } else if (curr_leaf->prev_leaf != nullptr) { + curr_leaf = curr_leaf->prev_leaf; + curr_slot = curr_leaf->slotuse - 1; + } else { + // this is begin() + curr_slot = 0; + } + + return tmp; + } + + //! Equality of iterators. + bool operator==(const iterator &x) const { return (x.curr_leaf == curr_leaf) && (x.curr_slot == curr_slot); } + + //! Inequality of iterators. + bool operator!=(const iterator &x) const { return (x.curr_leaf != curr_leaf) || (x.curr_slot != curr_slot); } + }; + + //! STL-like read-only iterator object for B+ tree items. The iterator + //! points to a specific slot number in a leaf. + class const_iterator { + public: + // *** Types + + //! The key type of the btree. Returned by key(). + typedef typename BTree::key_type key_type; + + //! The value type of the btree. Returned by operator*(). + typedef typename BTree::value_type value_type; + + //! Reference to the value_type. STL required. + typedef const value_type &reference; + + //! Pointer to the value_type. STL required. + typedef const value_type *pointer; + + //! STL-magic iterator category + typedef std::bidirectional_iterator_tag iterator_category; + + //! STL-magic + typedef ptrdiff_t difference_type; + + //! Our own type + typedef const_iterator self; + + private: + // *** Members + + //! The currently referenced leaf node of the tree + const typename BTree::LeafNode *curr_leaf; + + //! Current key/data slot referenced + unsigned short curr_slot; + + //! Friendly to the reverse_const_iterator, so it may access the two + //! data items directly + friend class const_reverse_iterator; + + // The macro TLX_BTREE_FRIENDS can be used by outside class to access + // the B+ tree internals. This was added for wxBTreeDemo to be able to + // draw the tree. + TLX_BTREE_FRIENDS; + + public: + // *** Methods + + //! Default-Constructor of a const iterator + const_iterator() : curr_leaf(nullptr), curr_slot(0) {} + + //! Initializing-Constructor of a const iterator + const_iterator(const typename BTree::LeafNode *l, unsigned short s) : curr_leaf(l), curr_slot(s) {} + + //! Copy-constructor from a mutable iterator + const_iterator(const iterator &it) // NOLINT + : curr_leaf(it.curr_leaf), curr_slot(it.curr_slot) {} + + //! Copy-constructor from a mutable reverse iterator + const_iterator(const reverse_iterator &it) // NOLINT + : curr_leaf(it.curr_leaf), curr_slot(it.curr_slot) {} + + //! Copy-constructor from a const reverse iterator + const_iterator(const const_reverse_iterator &it) // NOLINT + : curr_leaf(it.curr_leaf), curr_slot(it.curr_slot) {} + + //! Dereference the iterator. + reference operator*() const { return curr_leaf->slotdata[curr_slot]; } + + //! Dereference the iterator. + pointer operator->() const { return &curr_leaf->slotdata[curr_slot]; } + + //! Key of the current slot. + const key_type &key() const { return curr_leaf->key(curr_slot); } + + //! Prefix++ advance the iterator to the next slot. + const_iterator &operator++() { + if (curr_slot + 1u < curr_leaf->slotuse) { + ++curr_slot; + } else if (curr_leaf->next_leaf != nullptr) { + curr_leaf = curr_leaf->next_leaf; + curr_slot = 0; + } else { + // this is end() + curr_slot = curr_leaf->slotuse; + } + + return *this; + } + + //! Postfix++ advance the iterator to the next slot. + const_iterator operator++(int) { + const_iterator tmp = *this; // copy ourselves + + if (curr_slot + 1u < curr_leaf->slotuse) { + ++curr_slot; + } else if (curr_leaf->next_leaf != nullptr) { + curr_leaf = curr_leaf->next_leaf; + curr_slot = 0; + } else { + // this is end() + curr_slot = curr_leaf->slotuse; + } + + return tmp; + } + + //! Prefix-- backstep the iterator to the last slot. + const_iterator &operator--() { + if (curr_slot > 0) { + --curr_slot; + } else if (curr_leaf->prev_leaf != nullptr) { + curr_leaf = curr_leaf->prev_leaf; + curr_slot = curr_leaf->slotuse - 1; + } else { + // this is begin() + curr_slot = 0; + } + + return *this; + } + + //! Postfix-- backstep the iterator to the last slot. + const_iterator operator--(int) { + const_iterator tmp = *this; // copy ourselves + + if (curr_slot > 0) { + --curr_slot; + } else if (curr_leaf->prev_leaf != nullptr) { + curr_leaf = curr_leaf->prev_leaf; + curr_slot = curr_leaf->slotuse - 1; + } else { + // this is begin() + curr_slot = 0; + } + + return tmp; + } + + //! Equality of iterators. + bool operator==(const const_iterator &x) const { return (x.curr_leaf == curr_leaf) && (x.curr_slot == curr_slot); } + + //! Inequality of iterators. + bool operator!=(const const_iterator &x) const { return (x.curr_leaf != curr_leaf) || (x.curr_slot != curr_slot); } + }; + + //! STL-like mutable reverse iterator object for B+ tree items. The + //! iterator points to a specific slot number in a leaf. + class reverse_iterator { + public: + // *** Types + + //! The key type of the btree. Returned by key(). + typedef typename BTree::key_type key_type; + + //! The value type of the btree. Returned by operator*(). + typedef typename BTree::value_type value_type; + + //! Reference to the value_type. STL required. + typedef value_type &reference; + + //! Pointer to the value_type. STL required. + typedef value_type *pointer; + + //! STL-magic iterator category + typedef std::bidirectional_iterator_tag iterator_category; + + //! STL-magic + typedef ptrdiff_t difference_type; + + //! Our own type + typedef reverse_iterator self; + + private: + // *** Members + + //! The currently referenced leaf node of the tree + typename BTree::LeafNode *curr_leaf; + + //! One slot past the current key/data slot referenced. + unsigned short curr_slot; + + //! Friendly to the const_iterator, so it may access the two data items + //! directly + friend class iterator; + + //! Also friendly to the const_iterator, so it may access the two data + //! items directly + friend class const_iterator; + + //! Also friendly to the const_iterator, so it may access the two data + //! items directly + friend class const_reverse_iterator; + + // The macro TLX_BTREE_FRIENDS can be used by outside class to access + // the B+ tree internals. This was added for wxBTreeDemo to be able to + // draw the tree. + TLX_BTREE_FRIENDS; + + public: + // *** Methods + + //! Default-Constructor of a reverse iterator + reverse_iterator() : curr_leaf(nullptr), curr_slot(0) {} + + //! Initializing-Constructor of a mutable reverse iterator + reverse_iterator(typename BTree::LeafNode *l, unsigned short s) : curr_leaf(l), curr_slot(s) {} + + //! Copy-constructor from a mutable iterator + reverse_iterator(const iterator &it) // NOLINT + : curr_leaf(it.curr_leaf), curr_slot(it.curr_slot) {} + + //! Dereference the iterator. + reference operator*() const { + TLX_BTREE_ASSERT(curr_slot > 0); + return curr_leaf->slotdata[curr_slot - 1]; + } + + //! Dereference the iterator. + pointer operator->() const { + TLX_BTREE_ASSERT(curr_slot > 0); + return &curr_leaf->slotdata[curr_slot - 1]; + } + + //! Key of the current slot. + const key_type &key() const { + TLX_BTREE_ASSERT(curr_slot > 0); + return curr_leaf->key(curr_slot - 1); + } + + //! Prefix++ advance the iterator to the next slot. + reverse_iterator &operator++() { + if (curr_slot > 1) { + --curr_slot; + } else if (curr_leaf->prev_leaf != nullptr) { + curr_leaf = curr_leaf->prev_leaf; + curr_slot = curr_leaf->slotuse; + } else { + // this is begin() == rend() + curr_slot = 0; + } + + return *this; + } + + //! Postfix++ advance the iterator to the next slot. + reverse_iterator operator++(int) { + reverse_iterator tmp = *this; // copy ourselves + + if (curr_slot > 1) { + --curr_slot; + } else if (curr_leaf->prev_leaf != nullptr) { + curr_leaf = curr_leaf->prev_leaf; + curr_slot = curr_leaf->slotuse; + } else { + // this is begin() == rend() + curr_slot = 0; + } + + return tmp; + } + + //! Prefix-- backstep the iterator to the last slot. + reverse_iterator &operator--() { + if (curr_slot < curr_leaf->slotuse) { + ++curr_slot; + } else if (curr_leaf->next_leaf != nullptr) { + curr_leaf = curr_leaf->next_leaf; + curr_slot = 1; + } else { + // this is end() == rbegin() + curr_slot = curr_leaf->slotuse; + } + + return *this; + } + + //! Postfix-- backstep the iterator to the last slot. + reverse_iterator operator--(int) { + reverse_iterator tmp = *this; // copy ourselves + + if (curr_slot < curr_leaf->slotuse) { + ++curr_slot; + } else if (curr_leaf->next_leaf != nullptr) { + curr_leaf = curr_leaf->next_leaf; + curr_slot = 1; + } else { + // this is end() == rbegin() + curr_slot = curr_leaf->slotuse; + } + + return tmp; + } + + //! Equality of iterators. + bool operator==(const reverse_iterator &x) const { + return (x.curr_leaf == curr_leaf) && (x.curr_slot == curr_slot); + } + + //! Inequality of iterators. + bool operator!=(const reverse_iterator &x) const { + return (x.curr_leaf != curr_leaf) || (x.curr_slot != curr_slot); + } + }; + + //! STL-like read-only reverse iterator object for B+ tree items. The + //! iterator points to a specific slot number in a leaf. + class const_reverse_iterator { + public: + // *** Types + + //! The key type of the btree. Returned by key(). + typedef typename BTree::key_type key_type; + + //! The value type of the btree. Returned by operator*(). + typedef typename BTree::value_type value_type; + + //! Reference to the value_type. STL required. + typedef const value_type &reference; + + //! Pointer to the value_type. STL required. + typedef const value_type *pointer; + + //! STL-magic iterator category + typedef std::bidirectional_iterator_tag iterator_category; + + //! STL-magic + typedef ptrdiff_t difference_type; + + //! Our own type + typedef const_reverse_iterator self; + + private: + // *** Members + + //! The currently referenced leaf node of the tree + const typename BTree::LeafNode *curr_leaf; + + //! One slot past the current key/data slot referenced. + unsigned short curr_slot; + + //! Friendly to the const_iterator, so it may access the two data items + //! directly. + friend class reverse_iterator; + + // The macro TLX_BTREE_FRIENDS can be used by outside class to access + // the B+ tree internals. This was added for wxBTreeDemo to be able to + // draw the tree. + TLX_BTREE_FRIENDS; + + public: + // *** Methods + + //! Default-Constructor of a const reverse iterator. + const_reverse_iterator() : curr_leaf(nullptr), curr_slot(0) {} + + //! Initializing-Constructor of a const reverse iterator. + const_reverse_iterator(const typename BTree::LeafNode *l, unsigned short s) : curr_leaf(l), curr_slot(s) {} + + //! Copy-constructor from a mutable iterator. + const_reverse_iterator(const iterator &it) // NOLINT + : curr_leaf(it.curr_leaf), curr_slot(it.curr_slot) {} + + //! Copy-constructor from a const iterator. + const_reverse_iterator(const const_iterator &it) // NOLINT + : curr_leaf(it.curr_leaf), curr_slot(it.curr_slot) {} + + //! Copy-constructor from a mutable reverse iterator. + const_reverse_iterator(const reverse_iterator &it) // NOLINT + : curr_leaf(it.curr_leaf), curr_slot(it.curr_slot) {} + + //! Dereference the iterator. + reference operator*() const { + TLX_BTREE_ASSERT(curr_slot > 0); + return curr_leaf->slotdata[curr_slot - 1]; + } + + //! Dereference the iterator. + pointer operator->() const { + TLX_BTREE_ASSERT(curr_slot > 0); + return &curr_leaf->slotdata[curr_slot - 1]; + } + + //! Key of the current slot. + const key_type &key() const { + TLX_BTREE_ASSERT(curr_slot > 0); + return curr_leaf->key(curr_slot - 1); + } + + //! Prefix++ advance the iterator to the previous slot. + const_reverse_iterator &operator++() { + if (curr_slot > 1) { + --curr_slot; + } else if (curr_leaf->prev_leaf != nullptr) { + curr_leaf = curr_leaf->prev_leaf; + curr_slot = curr_leaf->slotuse; + } else { + // this is begin() == rend() + curr_slot = 0; + } + + return *this; + } + + //! Postfix++ advance the iterator to the previous slot. + const_reverse_iterator operator++(int) { + const_reverse_iterator tmp = *this; // copy ourselves + + if (curr_slot > 1) { + --curr_slot; + } else if (curr_leaf->prev_leaf != nullptr) { + curr_leaf = curr_leaf->prev_leaf; + curr_slot = curr_leaf->slotuse; + } else { + // this is begin() == rend() + curr_slot = 0; + } + + return tmp; + } + + //! Prefix-- backstep the iterator to the next slot. + const_reverse_iterator &operator--() { + if (curr_slot < curr_leaf->slotuse) { + ++curr_slot; + } else if (curr_leaf->next_leaf != nullptr) { + curr_leaf = curr_leaf->next_leaf; + curr_slot = 1; + } else { + // this is end() == rbegin() + curr_slot = curr_leaf->slotuse; + } + + return *this; + } + + //! Postfix-- backstep the iterator to the next slot. + const_reverse_iterator operator--(int) { + const_reverse_iterator tmp = *this; // copy ourselves + + if (curr_slot < curr_leaf->slotuse) { + ++curr_slot; + } else if (curr_leaf->next_leaf != nullptr) { + curr_leaf = curr_leaf->next_leaf; + curr_slot = 1; + } else { + // this is end() == rbegin() + curr_slot = curr_leaf->slotuse; + } + + return tmp; + } + + //! Equality of iterators. + bool operator==(const const_reverse_iterator &x) const { + return (x.curr_leaf == curr_leaf) && (x.curr_slot == curr_slot); + } + + //! Inequality of iterators. + bool operator!=(const const_reverse_iterator &x) const { + return (x.curr_leaf != curr_leaf) || (x.curr_slot != curr_slot); + } + }; + + //! \} + + public: + //! \name Small Statistics Structure + //! \{ + + /*! + * A small struct containing basic statistics about the B+ tree. It can be + * fetched using get_stats(). + */ + struct tree_stats { + //! Number of items in the B+ tree + size_type size; + + //! Number of leaves in the B+ tree + size_type leaves; + + //! Number of inner nodes in the B+ tree + size_type inner_nodes; + + //! Base B+ tree parameter: The number of key/data slots in each leaf + static const unsigned short leaf_slots = Self::leaf_slotmax; + + //! Base B+ tree parameter: The number of key slots in each inner node. + static const unsigned short inner_slots = Self::inner_slotmax; + + //! Zero initialized + tree_stats() : size(0), leaves(0), inner_nodes(0) {} + + //! Return the total number of nodes + size_type nodes() const { return inner_nodes + leaves; } + + //! Return the average fill of leaves + double avgfill_leaves() const { return static_cast(size) / (leaves * leaf_slots); } + }; + + //! \} + + private: + //! \name Tree Object Data Members + //! \{ + + //! Pointer to the B+ tree's root node, either leaf or inner node. + node *root_; + + //! Pointer to first leaf in the double linked leaf chain. + LeafNode *head_leaf_; + + //! Pointer to last leaf in the double linked leaf chain. + LeafNode *tail_leaf_; + + //! Other small statistics about the B+ tree. + tree_stats stats_; + + //! Key comparison object. More comparison functions are generated from + //! this < relation. + key_compare key_less_; + + //! Memory allocator. + allocator_type allocator_; + + //! \} + + public: + //! \name Constructors and Destructor + //! \{ + + //! Default constructor initializing an empty B+ tree with the standard key + //! comparison function. + explicit BTree(const allocator_type &alloc = allocator_type()) + : root_(nullptr), head_leaf_(nullptr), tail_leaf_(nullptr), allocator_(alloc) {} + + //! Constructor initializing an empty B+ tree with a special key + //! comparison object. + explicit BTree(const key_compare &kcf, const allocator_type &alloc = allocator_type()) + : root_(nullptr), head_leaf_(nullptr), tail_leaf_(nullptr), key_less_(kcf), allocator_(alloc) {} + + //! Constructor initializing a B+ tree with the range [first,last). The + //! range need not be sorted. To create a B+ tree from a sorted range, use + //! bulk_load(). + template + BTree(InputIterator first, InputIterator last, const allocator_type &alloc = allocator_type()) + : root_(nullptr), head_leaf_(nullptr), tail_leaf_(nullptr), allocator_(alloc) { + insert(first, last); + } + + //! Constructor initializing a B+ tree with the range [first,last) and a + //! special key comparison object. The range need not be sorted. To create + //! a B+ tree from a sorted range, use bulk_load(). + template + BTree(InputIterator first, InputIterator last, const key_compare &kcf, const allocator_type &alloc = allocator_type()) + : root_(nullptr), head_leaf_(nullptr), tail_leaf_(nullptr), key_less_(kcf), allocator_(alloc) { + insert(first, last); + } + + //! Frees up all used B+ tree memory pages + ~BTree() { clear(); } + + //! Fast swapping of two identical B+ tree objects. + void swap(BTree &from) { + std::swap(root_, from.root_); + std::swap(head_leaf_, from.head_leaf_); + std::swap(tail_leaf_, from.tail_leaf_); + std::swap(stats_, from.stats_); + std::swap(key_less_, from.key_less_); + std::swap(allocator_, from.allocator_); + } + + //! \} + + public: + //! \name Key and Value Comparison Function Objects + //! \{ + + //! Function class to compare value_type objects. Required by the STL + class value_compare { + protected: + //! Key comparison function from the template parameter + key_compare key_comp; + + //! Constructor called from BTree::value_comp() + explicit value_compare(key_compare kc) : key_comp(kc) {} + + //! Friendly to the btree class so it may call the constructor + friend class BTree; + + public: + //! Function call "less"-operator resulting in true if x < y. + bool operator()(const value_type &x, const value_type &y) const { return key_comp(x.first, y.first); } + }; + + //! Constant access to the key comparison object sorting the B+ tree. + key_compare key_comp() const { return key_less_; } + + //! Constant access to a constructed value_type comparison object. Required + //! by the STL. + value_compare value_comp() const { return value_compare(key_less_); } + + //! \} + + private: + //! \name Convenient Key Comparison Functions Generated From key_less + //! \{ + + //! True if a < b ? "constructed" from key_less_() + bool key_less(const key_type &a, const key_type &b) const { return key_less_(a, b); } + + //! True if a <= b ? constructed from key_less() + bool key_lessequal(const key_type &a, const key_type &b) const { return !key_less_(b, a); } + + //! True if a > b ? constructed from key_less() + bool key_greater(const key_type &a, const key_type &b) const { return key_less_(b, a); } + + //! True if a >= b ? constructed from key_less() + bool key_greaterequal(const key_type &a, const key_type &b) const { return !key_less_(a, b); } + + //! True if a == b ? constructed from key_less(). This requires the < + //! relation to be a total order, otherwise the B+ tree cannot be sorted. + bool key_equal(const key_type &a, const key_type &b) const { return !key_less_(a, b) && !key_less_(b, a); } + + //! \} + + public: + //! \name Allocators + //! \{ + + //! Return the base node allocator provided during construction. + allocator_type get_allocator() const { return allocator_; } + + //! \} + + private: + //! \name Node Object Allocation and Deallocation Functions + //! \{ + + //! Return an allocator for LeafNode objects. + typename LeafNode::alloc_type leaf_node_allocator() { return typename LeafNode::alloc_type(allocator_); } + + //! Return an allocator for InnerNode objects. + typename InnerNode::alloc_type inner_node_allocator() { return typename InnerNode::alloc_type(allocator_); } + + //! Allocate and initialize a leaf node + LeafNode *allocate_leaf() { + LeafNode *n = new (leaf_node_allocator().allocate(1)) LeafNode(); + n->initialize(); + stats_.leaves++; + return n; + } + + //! Allocate and initialize an inner node + InnerNode *allocate_inner(unsigned short level) { + InnerNode *n = new (inner_node_allocator().allocate(1)) InnerNode(); + n->initialize(level); + stats_.inner_nodes++; + return n; + } + + //! Correctly free either inner or leaf node, destructs all contained key + //! and value objects. + void free_node(node *n) { + if (n->is_leafnode()) { + LeafNode *ln = static_cast(n); + typename LeafNode::alloc_type a(leaf_node_allocator()); + std::allocator_traits::destroy(a, ln); + std::allocator_traits::deallocate(a, ln, 1); + stats_.leaves--; + } else { + InnerNode *in = static_cast(n); + typename InnerNode::alloc_type a(inner_node_allocator()); + std::allocator_traits::destroy(a, in); + std::allocator_traits::deallocate(a, in, 1); + stats_.inner_nodes--; + } + } + + //! \} + + public: + //! \name Fast Destruction of the B+ Tree + //! \{ + + //! Frees all key/data pairs and all nodes of the tree. + void clear() { + if (root_) { + clear_recursive(root_); + free_node(root_); + + root_ = nullptr; + head_leaf_ = tail_leaf_ = nullptr; + + stats_ = tree_stats(); + } + + TLX_BTREE_ASSERT(stats_.size == 0); + } + + private: + //! Recursively free up nodes. + void clear_recursive(node *n) { + if (n->is_leafnode()) { + LeafNode *leafnode = static_cast(n); + + for (unsigned short slot = 0; slot < leafnode->slotuse; ++slot) { + // data objects are deleted by LeafNode's destructor + } + } else { + InnerNode *innernode = static_cast(n); + + for (unsigned short slot = 0; slot < innernode->slotuse + 1; ++slot) { + clear_recursive(innernode->childid[slot]); + free_node(innernode->childid[slot]); + } + } + } + + //! \} + + public: + //! \name STL Iterator Construction Functions + //! \{ + + //! Constructs a read/data-write iterator that points to the first slot in + //! the first leaf of the B+ tree. + iterator begin() { return iterator(head_leaf_, 0); } + + //! Constructs a read/data-write iterator that points to the first invalid + //! slot in the last leaf of the B+ tree. + iterator end() { return iterator(tail_leaf_, tail_leaf_ ? tail_leaf_->slotuse : 0); } + + //! Constructs a read-only constant iterator that points to the first slot + //! in the first leaf of the B+ tree. + const_iterator begin() const { return const_iterator(head_leaf_, 0); } + + //! Constructs a read-only constant iterator that points to the first + //! invalid slot in the last leaf of the B+ tree. + const_iterator end() const { return const_iterator(tail_leaf_, tail_leaf_ ? tail_leaf_->slotuse : 0); } + + //! Constructs a read/data-write reverse iterator that points to the first + //! invalid slot in the last leaf of the B+ tree. Uses STL magic. + reverse_iterator rbegin() { return reverse_iterator(end()); } + + //! Constructs a read/data-write reverse iterator that points to the first + //! slot in the first leaf of the B+ tree. Uses STL magic. + reverse_iterator rend() { return reverse_iterator(begin()); } + + //! Constructs a read-only reverse iterator that points to the first + //! invalid slot in the last leaf of the B+ tree. Uses STL magic. + const_reverse_iterator rbegin() const { return const_reverse_iterator(end()); } + + //! Constructs a read-only reverse iterator that points to the first slot + //! in the first leaf of the B+ tree. Uses STL magic. + const_reverse_iterator rend() const { return const_reverse_iterator(begin()); } + + //! \} + + private: + //! \name B+ Tree Node Binary Search Functions + //! \{ + + //! Searches for the first key in the node n greater or equal to key. Uses + //! binary search with an optional linear self-verification. This is a + //! template function, because the slotkey array is located at different + //! places in LeafNode and InnerNode. + template + unsigned short find_lower(const node_type *n, const key_type &key) const { + if (sizeof(*n) > traits::binsearch_threshold) { + if (n->slotuse == 0) return 0; + + unsigned short lo = 0, hi = n->slotuse; + + while (lo < hi) { + unsigned short mid = (lo + hi) >> 1; + + if (key_lessequal(key, n->key(mid))) { + hi = mid; // key <= mid + } else { + lo = mid + 1; // key > mid + } + } + + TLX_BTREE_PRINT("BTree::find_lower: on " << n << " key " << key << " -> " << lo << " / " << hi); + + // verify result using simple linear search + if (self_verify) { + unsigned short i = 0; + while (i < n->slotuse && key_less(n->key(i), key)) ++i; + + TLX_BTREE_PRINT("BTree::find_lower: testfind: " << i); + TLX_BTREE_ASSERT(i == lo); + } + + return lo; + } else // for nodes <= binsearch_threshold do linear search. + { + unsigned short lo = 0; + while (lo < n->slotuse && key_less(n->key(lo), key)) ++lo; + return lo; + } + } + + //! Searches for the first key in the node n greater than key. Uses binary + //! search with an optional linear self-verification. This is a template + //! function, because the slotkey array is located at different places in + //! LeafNode and InnerNode. + template + unsigned short find_upper(const node_type *n, const key_type &key) const { + if (sizeof(*n) > traits::binsearch_threshold) { + if (n->slotuse == 0) return 0; + + unsigned short lo = 0, hi = n->slotuse; + + while (lo < hi) { + unsigned short mid = (lo + hi) >> 1; + + if (key_less(key, n->key(mid))) { + hi = mid; // key < mid + } else { + lo = mid + 1; // key >= mid + } + } + + TLX_BTREE_PRINT("BTree::find_upper: on " << n << " key " << key << " -> " << lo << " / " << hi); + + // verify result using simple linear search + if (self_verify) { + unsigned short i = 0; + while (i < n->slotuse && key_lessequal(n->key(i), key)) ++i; + + TLX_BTREE_PRINT("BTree::find_upper testfind: " << i); + TLX_BTREE_ASSERT(i == hi); + } + + return lo; + } else // for nodes <= binsearch_threshold do linear search. + { + unsigned short lo = 0; + while (lo < n->slotuse && key_lessequal(n->key(lo), key)) ++lo; + return lo; + } + } + + //! \} + + public: + //! \name Access Functions to the Item Count + //! \{ + + //! Return the number of key/data pairs in the B+ tree + size_type size() const { return stats_.size; } + + //! Returns true if there is at least one key/data pair in the B+ tree + bool empty() const { return (size() == size_type(0)); } + + //! Returns the largest possible size of the B+ Tree. This is just a + //! function required by the STL standard, the B+ Tree can hold more items. + size_type max_size() const { return size_type(-1); } + + //! Return a const reference to the current statistics. + const struct tree_stats &get_stats() const { return stats_; } + + //! \} + + public: + //! \name STL Access Functions Querying the Tree by Descending to a Leaf + //! \{ + + //! Non-STL function checking whether a key is in the B+ tree. The same as + //! (find(k) != end()) or (count() != 0). + bool exists(const key_type &key) const { + const node *n = root_; + if (!n) return false; + + while (!n->is_leafnode()) { + const InnerNode *inner = static_cast(n); + unsigned short slot = find_lower(inner, key); + + n = inner->childid[slot]; + } + + const LeafNode *leaf = static_cast(n); + + unsigned short slot = find_lower(leaf, key); + return (slot < leaf->slotuse && key_equal(key, leaf->key(slot))); + } + + //! Tries to locate a key in the B+ tree and returns an iterator to the + //! key/data slot if found. If unsuccessful it returns end(). + iterator find(const key_type &key) { + node *n = root_; + if (!n) return end(); + + while (!n->is_leafnode()) { + const InnerNode *inner = static_cast(n); + unsigned short slot = find_lower(inner, key); + + n = inner->childid[slot]; + } + + LeafNode *leaf = static_cast(n); + + unsigned short slot = find_lower(leaf, key); + return (slot < leaf->slotuse && key_equal(key, leaf->key(slot))) ? iterator(leaf, slot) : end(); + } + + //! Tries to locate a key in the B+ tree and returns an constant iterator to + //! the key/data slot if found. If unsuccessful it returns end(). + const_iterator find(const key_type &key) const { + const node *n = root_; + if (!n) return end(); + + while (!n->is_leafnode()) { + const InnerNode *inner = static_cast(n); + unsigned short slot = find_lower(inner, key); + + n = inner->childid[slot]; + } + + const LeafNode *leaf = static_cast(n); + + unsigned short slot = find_lower(leaf, key); + return (slot < leaf->slotuse && key_equal(key, leaf->key(slot))) ? const_iterator(leaf, slot) : end(); + } + + //! Tries to locate a key in the B+ tree and returns the number of identical + //! key entries found. + size_type count(const key_type &key) const { + const node *n = root_; + if (!n) return 0; + + while (!n->is_leafnode()) { + const InnerNode *inner = static_cast(n); + unsigned short slot = find_lower(inner, key); + + n = inner->childid[slot]; + } + + const LeafNode *leaf = static_cast(n); + + unsigned short slot = find_lower(leaf, key); + size_type num = 0; + + while (leaf && slot < leaf->slotuse && key_equal(key, leaf->key(slot))) { + ++num; + if (++slot >= leaf->slotuse) { + leaf = leaf->next_leaf; + slot = 0; + } + } + + return num; + } + + //! Searches the B+ tree and returns an iterator to the first pair equal to + //! or greater than key, or end() if all keys are smaller. + iterator lower_bound(const key_type &key) { + node *n = root_; + if (!n) return end(); + + while (!n->is_leafnode()) { + const InnerNode *inner = static_cast(n); + unsigned short slot = find_lower(inner, key); + + n = inner->childid[slot]; + } + + LeafNode *leaf = static_cast(n); + + unsigned short slot = find_lower(leaf, key); + return iterator(leaf, slot); + } + + //! Searches the B+ tree and returns a constant iterator to the first pair + //! equal to or greater than key, or end() if all keys are smaller. + const_iterator lower_bound(const key_type &key) const { + const node *n = root_; + if (!n) return end(); + + while (!n->is_leafnode()) { + const InnerNode *inner = static_cast(n); + unsigned short slot = find_lower(inner, key); + + n = inner->childid[slot]; + } + + const LeafNode *leaf = static_cast(n); + + unsigned short slot = find_lower(leaf, key); + return const_iterator(leaf, slot); + } + + //! Searches the B+ tree and returns an iterator to the first pair greater + //! than key, or end() if all keys are smaller or equal. + iterator upper_bound(const key_type &key) { + node *n = root_; + if (!n) return end(); + + while (!n->is_leafnode()) { + const InnerNode *inner = static_cast(n); + unsigned short slot = find_upper(inner, key); + + n = inner->childid[slot]; + } + + LeafNode *leaf = static_cast(n); + + unsigned short slot = find_upper(leaf, key); + return iterator(leaf, slot); + } + + //! Searches the B+ tree and returns a constant iterator to the first pair + //! greater than key, or end() if all keys are smaller or equal. + const_iterator upper_bound(const key_type &key) const { + const node *n = root_; + if (!n) return end(); + + while (!n->is_leafnode()) { + const InnerNode *inner = static_cast(n); + unsigned short slot = find_upper(inner, key); + + n = inner->childid[slot]; + } + + const LeafNode *leaf = static_cast(n); + + unsigned short slot = find_upper(leaf, key); + return const_iterator(leaf, slot); + } + + //! Searches the B+ tree and returns both lower_bound() and upper_bound(). + std::pair equal_range(const key_type &key) { + return std::pair(lower_bound(key), upper_bound(key)); + } + + //! Searches the B+ tree and returns both lower_bound() and upper_bound(). + std::pair equal_range(const key_type &key) const { + return std::pair(lower_bound(key), upper_bound(key)); + } + + //! \} + + public: + //! \name B+ Tree Object Comparison Functions + //! \{ + + //! Equality relation of B+ trees of the same type. B+ trees of the same + //! size and equal elements (both key and data) are considered equal. Beware + //! of the random ordering of duplicate keys. + bool operator==(const BTree &other) const { + return (size() == other.size()) && std::equal(begin(), end(), other.begin()); + } + + //! Inequality relation. Based on operator==. + bool operator!=(const BTree &other) const { return !(*this == other); } + + //! Total ordering relation of B+ trees of the same type. It uses + //! std::lexicographical_compare() for the actual comparison of elements. + bool operator<(const BTree &other) const { + return std::lexicographical_compare(begin(), end(), other.begin(), other.end()); + } + + //! Greater relation. Based on operator<. + bool operator>(const BTree &other) const { return other < *this; } + + //! Less-equal relation. Based on operator<. + bool operator<=(const BTree &other) const { return !(other < *this); } + + //! Greater-equal relation. Based on operator<. + bool operator>=(const BTree &other) const { return !(*this < other); } + + //! \} + + public: + //! \name Fast Copy: Assign Operator and Copy Constructors + //! \{ + + //! Assignment operator. All the key/data pairs are copied. + BTree &operator=(const BTree &other) { + if (this != &other) { + clear(); + + key_less_ = other.key_comp(); + allocator_ = other.get_allocator(); + + if (other.size() != 0) { + stats_.leaves = stats_.inner_nodes = 0; + if (other.root_) { + root_ = copy_recursive(other.root_); + } + stats_ = other.stats_; + } + + if (self_verify) verify(); + } + return *this; + } + + //! Copy constructor. The newly initialized B+ tree object will contain a + //! copy of all key/data pairs. + BTree(const BTree &other) + : root_(nullptr), + head_leaf_(nullptr), + tail_leaf_(nullptr), + stats_(other.stats_), + key_less_(other.key_comp()), + allocator_(other.get_allocator()) { + if (size() > 0) { + stats_.leaves = stats_.inner_nodes = 0; + if (other.root_) { + root_ = copy_recursive(other.root_); + } + if (self_verify) verify(); + } + } + + private: + //! Recursively copy nodes from another B+ tree object + struct node *copy_recursive(const node *n) { + if (n->is_leafnode()) { + const LeafNode *leaf = static_cast(n); + LeafNode *newleaf = allocate_leaf(); + + newleaf->slotuse = leaf->slotuse; + std::copy(leaf->slotdata, leaf->slotdata + leaf->slotuse, newleaf->slotdata); + + if (head_leaf_ == nullptr) { + head_leaf_ = tail_leaf_ = newleaf; + newleaf->prev_leaf = newleaf->next_leaf = nullptr; + } else { + newleaf->prev_leaf = tail_leaf_; + tail_leaf_->next_leaf = newleaf; + tail_leaf_ = newleaf; + } + + return newleaf; + } else { + const InnerNode *inner = static_cast(n); + InnerNode *newinner = allocate_inner(inner->level); + + newinner->slotuse = inner->slotuse; + std::copy(inner->slotkey, inner->slotkey + inner->slotuse, newinner->slotkey); + + for (unsigned short slot = 0; slot <= inner->slotuse; ++slot) { + newinner->childid[slot] = copy_recursive(inner->childid[slot]); + } + + return newinner; + } + } + + //! \} + + public: + //! \name Public Insertion Functions + //! \{ + + //! Attempt to insert a key/data pair into the B+ tree. If the tree does not + //! allow duplicate keys, then the insert may fail if it is already present. + std::pair insert(const value_type &x) { return insert_start(key_of_value::get(x), x); } + + //! Attempt to insert a key/data pair into the B+ tree. The iterator hint is + //! currently ignored by the B+ tree insertion routine. + iterator insert(iterator /* hint */, const value_type &x) { return insert_start(key_of_value::get(x), x).first; } + + //! Attempt to insert the range [first,last) of value_type pairs into the B+ + //! tree. Each key/data pair is inserted individually; to bulk load the + //! tree, use a constructor with range. + template + void insert(InputIterator first, InputIterator last) { + InputIterator iter = first; + while (iter != last) { + insert(*iter); + ++iter; + } + } + + //! \} + + private: + //! \name Private Insertion Functions + //! \{ + + //! Start the insertion descent at the current root and handle root splits. + //! Returns true if the item was inserted + std::pair insert_start(const key_type &key, const value_type &value) { + node *newchild = nullptr; + key_type newkey = key_type(); + + if (root_ == nullptr) { + root_ = head_leaf_ = tail_leaf_ = allocate_leaf(); + } + + std::pair r = insert_descend(root_, key, value, &newkey, &newchild); + + if (newchild) { + // this only occurs if insert_descend() could not insert the key + // into the root node, this mean the root is full and a new root + // needs to be created. + InnerNode *newroot = allocate_inner(root_->level + 1); + newroot->slotkey[0] = newkey; + + newroot->childid[0] = root_; + newroot->childid[1] = newchild; + + newroot->slotuse = 1; + + root_ = newroot; + } + + // increment size if the item was inserted + if (r.second) ++stats_.size; + +#ifdef TLX_BTREE_DEBUG + if (debug) print(std::cout); +#endif + + if (self_verify) { + verify(); + TLX_BTREE_ASSERT(exists(key)); + } + + return r; + } + + /*! + * Insert an item into the B+ tree. + * + * Descend down the nodes to a leaf, insert the key/data pair in a free + * slot. If the node overflows, then it must be split and the new split node + * inserted into the parent. Unroll / this splitting up to the root. + */ + std::pair insert_descend(node *n, const key_type &key, const value_type &value, key_type *splitkey, + node **splitnode) { + if (!n->is_leafnode()) { + InnerNode *inner = static_cast(n); + + key_type newkey = key_type(); + node *newchild = nullptr; + + unsigned short slot = find_lower(inner, key); + + TLX_BTREE_PRINT("BTree::insert_descend into " << inner->childid[slot]); + + std::pair r = insert_descend(inner->childid[slot], key, value, &newkey, &newchild); + + if (newchild) { + TLX_BTREE_PRINT("BTree::insert_descend newchild" + << " with key " << newkey << " node " << newchild << " at slot " << slot); + + if (inner->is_full()) { + split_inner_node(inner, splitkey, splitnode, slot); + + TLX_BTREE_PRINT("BTree::insert_descend done split_inner:" + << " putslot: " << slot << " putkey: " << newkey << " upkey: " << *splitkey); + +#ifdef TLX_BTREE_DEBUG + if (debug) { + print_node(std::cout, inner); + print_node(std::cout, *splitnode); + } +#endif + + // check if insert slot is in the split sibling node + TLX_BTREE_PRINT("BTree::insert_descend switch: " << slot << " > " << inner->slotuse + 1); + + if (slot == inner->slotuse + 1 && inner->slotuse < (*splitnode)->slotuse) { + // special case when the insert slot matches the split + // place between the two nodes, then the insert key + // becomes the split key. + + TLX_BTREE_ASSERT(inner->slotuse + 1 < inner_slotmax); + + InnerNode *split = static_cast(*splitnode); + + // move the split key and it's datum into the left node + inner->slotkey[inner->slotuse] = *splitkey; + inner->childid[inner->slotuse + 1] = split->childid[0]; + inner->slotuse++; + + // set new split key and move corresponding datum into + // right node + split->childid[0] = newchild; + *splitkey = newkey; + + return r; + } else if (slot >= inner->slotuse + 1) { + // in case the insert slot is in the newly create split + // node, we reuse the code below. + + slot -= inner->slotuse + 1; + inner = static_cast(*splitnode); + TLX_BTREE_PRINT( + "BTree::insert_descend switching to " + "splitted node " + << inner << " slot " << slot); + } + } + + // move items and put pointer to child node into correct slot + TLX_BTREE_ASSERT(slot >= 0 && slot <= inner->slotuse); + + std::copy_backward(inner->slotkey + slot, inner->slotkey + inner->slotuse, inner->slotkey + inner->slotuse + 1); + std::copy_backward(inner->childid + slot, inner->childid + inner->slotuse + 1, + inner->childid + inner->slotuse + 2); + + inner->slotkey[slot] = newkey; + inner->childid[slot + 1] = newchild; + inner->slotuse++; + } + + return r; + } else // n->is_leafnode() == true + { + LeafNode *leaf = static_cast(n); + + unsigned short slot = find_lower(leaf, key); + + if (!allow_duplicates && slot < leaf->slotuse && key_equal(key, leaf->key(slot))) { + return std::pair(iterator(leaf, slot), false); + } + + if (leaf->is_full()) { + split_leaf_node(leaf, splitkey, splitnode); + + // check if insert slot is in the split sibling node + if (slot >= leaf->slotuse) { + slot -= leaf->slotuse; + leaf = static_cast(*splitnode); + } + } + + // move items and put data item into correct data slot + TLX_BTREE_ASSERT(slot >= 0 && slot <= leaf->slotuse); + + std::copy_backward(leaf->slotdata + slot, leaf->slotdata + leaf->slotuse, leaf->slotdata + leaf->slotuse + 1); + + leaf->slotdata[slot] = value; + leaf->slotuse++; + + if (splitnode && leaf != *splitnode && slot == leaf->slotuse - 1) { + // special case: the node was split, and the insert is at the + // last slot of the old node. then the splitkey must be updated. + *splitkey = key; + } + + return std::pair(iterator(leaf, slot), true); + } + } + + //! Split up a leaf node into two equally-filled sibling leaves. Returns the + //! new nodes and it's insertion key in the two parameters. + void split_leaf_node(LeafNode *leaf, key_type *out_newkey, node **out_newleaf) { + TLX_BTREE_ASSERT(leaf->is_full()); + + unsigned short mid = (leaf->slotuse >> 1); + + TLX_BTREE_PRINT("BTree::split_leaf_node on " << leaf); + + LeafNode *newleaf = allocate_leaf(); + + newleaf->slotuse = leaf->slotuse - mid; + + newleaf->next_leaf = leaf->next_leaf; + if (newleaf->next_leaf == nullptr) { + TLX_BTREE_ASSERT(leaf == tail_leaf_); + tail_leaf_ = newleaf; + } else { + newleaf->next_leaf->prev_leaf = newleaf; + } + + std::copy(leaf->slotdata + mid, leaf->slotdata + leaf->slotuse, newleaf->slotdata); + + leaf->slotuse = mid; + leaf->next_leaf = newleaf; + newleaf->prev_leaf = leaf; + + *out_newkey = leaf->key(leaf->slotuse - 1); + *out_newleaf = newleaf; + } + + //! Split up an inner node into two equally-filled sibling nodes. Returns + //! the new nodes and it's insertion key in the two parameters. Requires the + //! slot of the item will be inserted, so the nodes will be the same size + //! after the insert. + void split_inner_node(InnerNode *inner, key_type *out_newkey, node **out_newinner, unsigned int addslot) { + TLX_BTREE_ASSERT(inner->is_full()); + + unsigned short mid = (inner->slotuse >> 1); + + TLX_BTREE_PRINT("BTree::split_inner: mid " << mid << " addslot " << addslot); + + // if the split is uneven and the overflowing item will be put into the + // larger node, then the smaller split node may underflow + if (addslot <= mid && mid > inner->slotuse - (mid + 1)) mid--; + + TLX_BTREE_PRINT("BTree::split_inner: mid " << mid << " addslot " << addslot); + + TLX_BTREE_PRINT("BTree::split_inner_node on " << inner << " into two nodes " << mid << " and " + << inner->slotuse - (mid + 1) << " sized"); + + InnerNode *newinner = allocate_inner(inner->level); + + newinner->slotuse = inner->slotuse - (mid + 1); + + std::copy(inner->slotkey + mid + 1, inner->slotkey + inner->slotuse, newinner->slotkey); + std::copy(inner->childid + mid + 1, inner->childid + inner->slotuse + 1, newinner->childid); + + inner->slotuse = mid; + + *out_newkey = inner->key(mid); + *out_newinner = newinner; + } + + //! \} + + public: + //! \name Bulk Loader - Construct Tree from Sorted Sequence + //! \{ + + //! Bulk load a sorted range. Loads items into leaves and constructs a + //! B-tree above them. The tree must be empty when calling this function. + template + void bulk_load(Iterator ibegin, Iterator iend) { + TLX_BTREE_ASSERT(empty()); + + stats_.size = iend - ibegin; + + // calculate number of leaves needed, round up. + size_t num_items = iend - ibegin; + size_t num_leaves = (num_items + leaf_slotmax - 1) / leaf_slotmax; + + TLX_BTREE_PRINT("BTree::bulk_load, level 0: " + << stats_.size << " items into " << num_leaves << " leaves with up to " + << ((iend - ibegin + num_leaves - 1) / num_leaves) << " items per leaf."); + + Iterator it = ibegin; + for (size_t i = 0; i < num_leaves; ++i) { + // allocate new leaf node + LeafNode *leaf = allocate_leaf(); + + // copy keys or (key,value) pairs into leaf nodes, uses template + // switch leaf->set_slot(). + leaf->slotuse = static_cast(num_items / (num_leaves - i)); + for (size_t s = 0; s < leaf->slotuse; ++s, ++it) leaf->set_slot(s, *it); + + if (tail_leaf_ != nullptr) { + tail_leaf_->next_leaf = leaf; + leaf->prev_leaf = tail_leaf_; + } else { + head_leaf_ = leaf; + } + tail_leaf_ = leaf; + + num_items -= leaf->slotuse; + } + + TLX_BTREE_ASSERT(it == iend && num_items == 0); + + // if the btree is so small to fit into one leaf, then we're done. + if (head_leaf_ == tail_leaf_) { + root_ = head_leaf_; + return; + } + + TLX_BTREE_ASSERT(stats_.leaves == num_leaves); + + // create first level of inner nodes, pointing to the leaves. + size_t num_parents = (num_leaves + (inner_slotmax + 1) - 1) / (inner_slotmax + 1); + + TLX_BTREE_PRINT("BTree::bulk_load, level 1: " + << num_leaves << " leaves in " << num_parents << " inner nodes with up to " + << ((num_leaves + num_parents - 1) / num_parents) << " leaves per inner node."); + + // save inner nodes and maxkey for next level. + typedef std::pair nextlevel_type; + nextlevel_type *nextlevel = new nextlevel_type[num_parents]; + + LeafNode *leaf = head_leaf_; + for (size_t i = 0; i < num_parents; ++i) { + // allocate new inner node at level 1 + InnerNode *n = allocate_inner(1); + + n->slotuse = static_cast(num_leaves / (num_parents - i)); + TLX_BTREE_ASSERT(n->slotuse > 0); + // this counts keys, but an inner node has keys+1 children. + --n->slotuse; + + // copy last key from each leaf and set child + for (unsigned short s = 0; s < n->slotuse; ++s) { + n->slotkey[s] = leaf->key(leaf->slotuse - 1); + n->childid[s] = leaf; + leaf = leaf->next_leaf; + } + n->childid[n->slotuse] = leaf; + + // track max key of any descendant. + nextlevel[i].first = n; + nextlevel[i].second = &leaf->key(leaf->slotuse - 1); + + leaf = leaf->next_leaf; + num_leaves -= n->slotuse + 1; + } + + TLX_BTREE_ASSERT(leaf == nullptr && num_leaves == 0); + + // recursively build inner nodes pointing to inner nodes. + for (int level = 2; num_parents != 1; ++level) { + size_t num_children = num_parents; + num_parents = (num_children + (inner_slotmax + 1) - 1) / (inner_slotmax + 1); + + TLX_BTREE_PRINT("BTree::bulk_load, level " + << level << ": " << num_children << " children in " << num_parents << " inner nodes with up to " + << ((num_children + num_parents - 1) / num_parents) << " children per inner node."); + + size_t inner_index = 0; + for (size_t i = 0; i < num_parents; ++i) { + // allocate new inner node at level + InnerNode *n = allocate_inner(level); + + n->slotuse = static_cast(num_children / (num_parents - i)); + TLX_BTREE_ASSERT(n->slotuse > 0); + // this counts keys, but an inner node has keys+1 children. + --n->slotuse; + + // copy children and maxkeys from nextlevel + for (unsigned short s = 0; s < n->slotuse; ++s) { + n->slotkey[s] = *nextlevel[inner_index].second; + n->childid[s] = nextlevel[inner_index].first; + ++inner_index; + } + n->childid[n->slotuse] = nextlevel[inner_index].first; + + // reuse nextlevel array for parents, because we can overwrite + // slots we've already consumed. + nextlevel[i].first = n; + nextlevel[i].second = nextlevel[inner_index].second; + + ++inner_index; + num_children -= n->slotuse + 1; + } + + TLX_BTREE_ASSERT(num_children == 0); + } + + root_ = nextlevel[0].first; + delete[] nextlevel; + + if (self_verify) verify(); + } + + //! \} + + private: + //! \name Support Class Encapsulating Deletion Results + //! \{ + + //! Result flags of recursive deletion. + enum result_flags_t { + //! Deletion successful and no fix-ups necessary. + btree_ok = 0, + + //! Deletion not successful because key was not found. + btree_not_found = 1, + + //! Deletion successful, the last key was updated so parent slotkeys + //! need updates. + btree_update_lastkey = 2, + + //! Deletion successful, children nodes were merged and the parent needs + //! to remove the empty node. + btree_fixmerge = 4 + }; + + //! B+ tree recursive deletion has much information which is needs to be + //! passed upward. + struct result_t { + //! Merged result flags + result_flags_t flags; + + //! The key to be updated at the parent's slot + key_type lastkey; + + //! Constructor of a result with a specific flag, this can also be used + //! as for implicit conversion. + result_t(result_flags_t f = btree_ok) // NOLINT + : flags(f), lastkey() {} + + //! Constructor with a lastkey value. + result_t(result_flags_t f, const key_type &k) : flags(f), lastkey(k) {} + + //! Test if this result object has a given flag set. + bool has(result_flags_t f) const { return (flags & f) != 0; } + + //! Merge two results OR-ing the result flags and overwriting lastkeys. + result_t &operator|=(const result_t &other) { + flags = result_flags_t(flags | other.flags); + + // we overwrite existing lastkeys on purpose + if (other.has(btree_update_lastkey)) lastkey = other.lastkey; + + return *this; + } + }; + + //! \} + + public: + //! \name Public Erase Functions + //! \{ + + //! Erases one (the first) of the key/data pairs associated with the given + //! key. + bool erase_one(const key_type &key) { + TLX_BTREE_PRINT("BTree::erase_one(" << key << ") on btree size " << size()); + + if (self_verify) verify(); + + if (!root_) return false; + + result_t result = erase_one_descend(key, root_, nullptr, nullptr, nullptr, nullptr, nullptr, 0); + + if (!result.has(btree_not_found)) --stats_.size; + +#ifdef TLX_BTREE_DEBUG + if (debug) print(std::cout); +#endif + if (self_verify) verify(); + + return !result.has(btree_not_found); + } + + //! Erases all the key/data pairs associated with the given key. This is + //! implemented using erase_one(). + size_type erase(const key_type &key) { + size_type c = 0; + + while (erase_one(key)) { + ++c; + if (!allow_duplicates) break; + } + + return c; + } + + //! Erase the key/data pair referenced by the iterator. + void erase(iterator iter) { + TLX_BTREE_PRINT("BTree::erase_iter(" << iter.curr_leaf << "," << iter.curr_slot << ") on btree size " << size()); + + if (self_verify) verify(); + + if (!root_) return; + + result_t result = erase_iter_descend(iter, root_, nullptr, nullptr, nullptr, nullptr, nullptr, 0); + + if (!result.has(btree_not_found)) --stats_.size; + +#ifdef TLX_BTREE_DEBUG + if (debug) print(std::cout); +#endif + if (self_verify) verify(); + } + +#ifdef BTREE_TODO + //! Erase all key/data pairs in the range [first,last). This function is + //! currently not implemented by the B+ Tree. + void erase(iterator /* first */, iterator /* last */) { abort(); } +#endif + + //! \} + + private: + //! \name Private Erase Functions + //! \{ + + /*! + * Erase one (the first) key/data pair in the B+ tree matching key. + * + * Descends down the tree in search of key. During the descent the parent, + * left and right siblings and their parents are computed and passed + * down. Once the key/data pair is found, it is removed from the leaf. If + * the leaf underflows 6 different cases are handled. These cases resolve + * the underflow by shifting key/data pairs from adjacent sibling nodes, + * merging two sibling nodes or trimming the tree. + */ + result_t erase_one_descend(const key_type &key, node *curr, node *left, node *right, InnerNode *left_parent, + InnerNode *right_parent, InnerNode *parent, unsigned int parentslot) { + if (curr->is_leafnode()) { + LeafNode *leaf = static_cast(curr); + LeafNode *left_leaf = static_cast(left); + LeafNode *right_leaf = static_cast(right); + + unsigned short slot = find_lower(leaf, key); + + if (slot >= leaf->slotuse || !key_equal(key, leaf->key(slot))) { + TLX_BTREE_PRINT("Could not find key " << key << " to erase."); + + return btree_not_found; + } + + TLX_BTREE_PRINT("Found key in leaf " << curr << " at slot " << slot); + + std::copy(leaf->slotdata + slot + 1, leaf->slotdata + leaf->slotuse, leaf->slotdata + slot); + + leaf->slotuse--; + + result_t myres = btree_ok; + + // if the last key of the leaf was changed, the parent is notified + // and updates the key of this leaf + if (slot == leaf->slotuse) { + if (parent && parentslot < parent->slotuse) { + TLX_BTREE_ASSERT(parent->childid[parentslot] == curr); + parent->slotkey[parentslot] = leaf->key(leaf->slotuse - 1); + } else { + if (leaf->slotuse >= 1) { + TLX_BTREE_PRINT("Scheduling lastkeyupdate: key " << leaf->key(leaf->slotuse - 1)); + myres |= result_t(btree_update_lastkey, leaf->key(leaf->slotuse - 1)); + } else { + TLX_BTREE_ASSERT(leaf == root_); + } + } + } + + if (leaf->is_underflow() && !(leaf == root_ && leaf->slotuse >= 1)) { + // determine what to do about the underflow + + // case : if this empty leaf is the root, then delete all nodes + // and set root to nullptr. + if (left_leaf == nullptr && right_leaf == nullptr) { + TLX_BTREE_ASSERT(leaf == root_); + TLX_BTREE_ASSERT(leaf->slotuse == 0); + + free_node(root_); + + root_ = leaf = nullptr; + head_leaf_ = tail_leaf_ = nullptr; + + // will be decremented soon by insert_start() + TLX_BTREE_ASSERT(stats_.size == 1); + TLX_BTREE_ASSERT(stats_.leaves == 0); + TLX_BTREE_ASSERT(stats_.inner_nodes == 0); + + return btree_ok; + } + // case : if both left and right leaves would underflow in case + // of a shift, then merging is necessary. choose the more local + // merger with our parent + else if ((left_leaf == nullptr || left_leaf->is_few()) && (right_leaf == nullptr || right_leaf->is_few())) { + if (left_parent == parent) + myres |= merge_leaves(left_leaf, leaf, left_parent); + else + myres |= merge_leaves(leaf, right_leaf, right_parent); + } + // case : the right leaf has extra data, so balance right with + // current + else if ((left_leaf != nullptr && left_leaf->is_few()) && (right_leaf != nullptr && !right_leaf->is_few())) { + if (right_parent == parent) + myres |= shift_left_leaf(leaf, right_leaf, right_parent, parentslot); + else + myres |= merge_leaves(left_leaf, leaf, left_parent); + } + // case : the left leaf has extra data, so balance left with + // current + else if ((left_leaf != nullptr && !left_leaf->is_few()) && (right_leaf != nullptr && right_leaf->is_few())) { + if (left_parent == parent) + shift_right_leaf(left_leaf, leaf, left_parent, parentslot - 1); + else + myres |= merge_leaves(leaf, right_leaf, right_parent); + } + // case : both the leaf and right leaves have extra data and our + // parent, choose the leaf with more data + else if (left_parent == right_parent) { + if (left_leaf->slotuse <= right_leaf->slotuse) + myres |= shift_left_leaf(leaf, right_leaf, right_parent, parentslot); + else + shift_right_leaf(left_leaf, leaf, left_parent, parentslot - 1); + } else { + if (left_parent == parent) + shift_right_leaf(left_leaf, leaf, left_parent, parentslot - 1); + else + myres |= shift_left_leaf(leaf, right_leaf, right_parent, parentslot); + } + } + + return myres; + } else // !curr->is_leafnode() + { + InnerNode *inner = static_cast(curr); + InnerNode *left_inner = static_cast(left); + InnerNode *right_inner = static_cast(right); + + node *myleft, *myright; + InnerNode *myleft_parent, *myright_parent; + + unsigned short slot = find_lower(inner, key); + + if (slot == 0) { + myleft = (left == nullptr) ? nullptr : static_cast(left)->childid[left->slotuse - 1]; + myleft_parent = left_parent; + } else { + myleft = inner->childid[slot - 1]; + myleft_parent = inner; + } + + if (slot == inner->slotuse) { + myright = (right == nullptr) ? nullptr : static_cast(right)->childid[0]; + myright_parent = right_parent; + } else { + myright = inner->childid[slot + 1]; + myright_parent = inner; + } + + TLX_BTREE_PRINT("erase_one_descend into " << inner->childid[slot]); + + result_t result = + erase_one_descend(key, inner->childid[slot], myleft, myright, myleft_parent, myright_parent, inner, slot); + + result_t myres = btree_ok; + + if (result.has(btree_not_found)) { + return result; + } + + if (result.has(btree_update_lastkey)) { + if (parent && parentslot < parent->slotuse) { + TLX_BTREE_PRINT("Fixing lastkeyupdate: key " << result.lastkey << " into parent " << parent + << " at parentslot " << parentslot); + + TLX_BTREE_ASSERT(parent->childid[parentslot] == curr); + parent->slotkey[parentslot] = result.lastkey; + } else { + TLX_BTREE_PRINT("Forwarding lastkeyupdate: key " << result.lastkey); + myres |= result_t(btree_update_lastkey, result.lastkey); + } + } + + if (result.has(btree_fixmerge)) { + // either the current node or the next is empty and should be + // removed + if (inner->childid[slot]->slotuse != 0) slot++; + + // this is the child slot invalidated by the merge + TLX_BTREE_ASSERT(inner->childid[slot]->slotuse == 0); + + free_node(inner->childid[slot]); + + std::copy(inner->slotkey + slot, inner->slotkey + inner->slotuse, inner->slotkey + slot - 1); + std::copy(inner->childid + slot + 1, inner->childid + inner->slotuse + 1, inner->childid + slot); + + inner->slotuse--; + + if (inner->level == 1) { + // fix split key for children leaves + slot--; + LeafNode *child = static_cast(inner->childid[slot]); + inner->slotkey[slot] = child->key(child->slotuse - 1); + } + } + + if (inner->is_underflow() && !(inner == root_ && inner->slotuse >= 1)) { + // case: the inner node is the root and has just one child. that + // child becomes the new root + if (left_inner == nullptr && right_inner == nullptr) { + TLX_BTREE_ASSERT(inner == root_); + TLX_BTREE_ASSERT(inner->slotuse == 0); + + root_ = inner->childid[0]; + + inner->slotuse = 0; + free_node(inner); + + return btree_ok; + } + // case : if both left and right leaves would underflow in case + // of a shift, then merging is necessary. choose the more local + // merger with our parent + else if ((left_inner == nullptr || left_inner->is_few()) && (right_inner == nullptr || right_inner->is_few())) { + if (left_parent == parent) + myres |= merge_inner(left_inner, inner, left_parent, parentslot - 1); + else + myres |= merge_inner(inner, right_inner, right_parent, parentslot); + } + // case : the right leaf has extra data, so balance right with + // current + else if ((left_inner != nullptr && left_inner->is_few()) && + (right_inner != nullptr && !right_inner->is_few())) { + if (right_parent == parent) + shift_left_inner(inner, right_inner, right_parent, parentslot); + else + myres |= merge_inner(left_inner, inner, left_parent, parentslot - 1); + } + // case : the left leaf has extra data, so balance left with + // current + else if ((left_inner != nullptr && !left_inner->is_few()) && + (right_inner != nullptr && right_inner->is_few())) { + if (left_parent == parent) + shift_right_inner(left_inner, inner, left_parent, parentslot - 1); + else + myres |= merge_inner(inner, right_inner, right_parent, parentslot); + } + // case : both the leaf and right leaves have extra data and our + // parent, choose the leaf with more data + else if (left_parent == right_parent) { + if (left_inner->slotuse <= right_inner->slotuse) + shift_left_inner(inner, right_inner, right_parent, parentslot); + else + shift_right_inner(left_inner, inner, left_parent, parentslot - 1); + } else { + if (left_parent == parent) + shift_right_inner(left_inner, inner, left_parent, parentslot - 1); + else + shift_left_inner(inner, right_inner, right_parent, parentslot); + } + } + + return myres; + } + } + + /*! + * Erase one key/data pair referenced by an iterator in the B+ tree. + * + * Descends down the tree in search of an iterator. During the descent the + * parent, left and right siblings and their parents are computed and passed + * down. The difficulty is that the iterator contains only a pointer to a + * LeafNode, which means that this function must do a recursive depth first + * search for that leaf node in the subtree containing all pairs of the same + * key. This subtree can be very large, even the whole tree, though in + * practice it would not make sense to have so many duplicate keys. + * + * Once the referenced key/data pair is found, it is removed from the leaf + * and the same underflow cases are handled as in erase_one_descend. + */ + result_t erase_iter_descend(const iterator &iter, node *curr, node *left, node *right, InnerNode *left_parent, + InnerNode *right_parent, InnerNode *parent, unsigned int parentslot) { + if (curr->is_leafnode()) { + LeafNode *leaf = static_cast(curr); + LeafNode *left_leaf = static_cast(left); + LeafNode *right_leaf = static_cast(right); + + // if this is not the correct leaf, get next step in recursive + // search + if (leaf != iter.curr_leaf) { + return btree_not_found; + } + + if (iter.curr_slot >= leaf->slotuse) { + TLX_BTREE_PRINT("Could not find iterator (" << iter.curr_leaf << "," << iter.curr_slot + << ") to erase. Invalid leaf node?"); + + return btree_not_found; + } + + unsigned short slot = iter.curr_slot; + + TLX_BTREE_PRINT("Found iterator in leaf " << curr << " at slot " << slot); + + std::copy(leaf->slotdata + slot + 1, leaf->slotdata + leaf->slotuse, leaf->slotdata + slot); + + leaf->slotuse--; + + result_t myres = btree_ok; + + // if the last key of the leaf was changed, the parent is notified + // and updates the key of this leaf + if (slot == leaf->slotuse) { + if (parent && parentslot < parent->slotuse) { + TLX_BTREE_ASSERT(parent->childid[parentslot] == curr); + parent->slotkey[parentslot] = leaf->key(leaf->slotuse - 1); + } else { + if (leaf->slotuse >= 1) { + TLX_BTREE_PRINT("Scheduling lastkeyupdate: key " << leaf->key(leaf->slotuse - 1)); + myres |= result_t(btree_update_lastkey, leaf->key(leaf->slotuse - 1)); + } else { + TLX_BTREE_ASSERT(leaf == root_); + } + } + } + + if (leaf->is_underflow() && !(leaf == root_ && leaf->slotuse >= 1)) { + // determine what to do about the underflow + + // case : if this empty leaf is the root, then delete all nodes + // and set root to nullptr. + if (left_leaf == nullptr && right_leaf == nullptr) { + TLX_BTREE_ASSERT(leaf == root_); + TLX_BTREE_ASSERT(leaf->slotuse == 0); + + free_node(root_); + + root_ = leaf = nullptr; + head_leaf_ = tail_leaf_ = nullptr; + + // will be decremented soon by insert_start() + TLX_BTREE_ASSERT(stats_.size == 1); + TLX_BTREE_ASSERT(stats_.leaves == 0); + TLX_BTREE_ASSERT(stats_.inner_nodes == 0); + + return btree_ok; + } + // case : if both left and right leaves would underflow in case + // of a shift, then merging is necessary. choose the more local + // merger with our parent + else if ((left_leaf == nullptr || left_leaf->is_few()) && (right_leaf == nullptr || right_leaf->is_few())) { + if (left_parent == parent) + myres |= merge_leaves(left_leaf, leaf, left_parent); + else + myres |= merge_leaves(leaf, right_leaf, right_parent); + } + // case : the right leaf has extra data, so balance right with + // current + else if ((left_leaf != nullptr && left_leaf->is_few()) && (right_leaf != nullptr && !right_leaf->is_few())) { + if (right_parent == parent) { + myres |= shift_left_leaf(leaf, right_leaf, right_parent, parentslot); + } else { + myres |= merge_leaves(left_leaf, leaf, left_parent); + } + } + // case : the left leaf has extra data, so balance left with + // current + else if ((left_leaf != nullptr && !left_leaf->is_few()) && (right_leaf != nullptr && right_leaf->is_few())) { + if (left_parent == parent) { + shift_right_leaf(left_leaf, leaf, left_parent, parentslot - 1); + } else { + myres |= merge_leaves(leaf, right_leaf, right_parent); + } + } + // case : both the leaf and right leaves have extra data and our + // parent, choose the leaf with more data + else if (left_parent == right_parent) { + if (left_leaf->slotuse <= right_leaf->slotuse) { + myres |= shift_left_leaf(leaf, right_leaf, right_parent, parentslot); + } else { + shift_right_leaf(left_leaf, leaf, left_parent, parentslot - 1); + } + } else { + if (left_parent == parent) { + shift_right_leaf(left_leaf, leaf, left_parent, parentslot - 1); + } else { + myres |= shift_left_leaf(leaf, right_leaf, right_parent, parentslot); + } + } + } + + return myres; + } else // !curr->is_leafnode() + { + InnerNode *inner = static_cast(curr); + InnerNode *left_inner = static_cast(left); + InnerNode *right_inner = static_cast(right); + + // find first slot below which the searched iterator might be + // located. + + result_t result; + unsigned short slot = find_lower(inner, iter.key()); + + while (slot <= inner->slotuse) { + node *myleft, *myright; + InnerNode *myleft_parent, *myright_parent; + + if (slot == 0) { + myleft = (left == nullptr) ? nullptr : static_cast(left)->childid[left->slotuse - 1]; + myleft_parent = left_parent; + } else { + myleft = inner->childid[slot - 1]; + myleft_parent = inner; + } + + if (slot == inner->slotuse) { + myright = (right == nullptr) ? nullptr : static_cast(right)->childid[0]; + myright_parent = right_parent; + } else { + myright = inner->childid[slot + 1]; + myright_parent = inner; + } + + TLX_BTREE_PRINT("erase_iter_descend into " << inner->childid[slot]); + + result = + erase_iter_descend(iter, inner->childid[slot], myleft, myright, myleft_parent, myright_parent, inner, slot); + + if (!result.has(btree_not_found)) break; + + // continue recursive search for leaf on next slot + + if (slot < inner->slotuse && key_less(inner->slotkey[slot], iter.key())) return btree_not_found; + + ++slot; + } + + if (slot > inner->slotuse) return btree_not_found; + + result_t myres = btree_ok; + + if (result.has(btree_update_lastkey)) { + if (parent && parentslot < parent->slotuse) { + TLX_BTREE_PRINT("Fixing lastkeyupdate: key " << result.lastkey << " into parent " << parent + << " at parentslot " << parentslot); + + TLX_BTREE_ASSERT(parent->childid[parentslot] == curr); + parent->slotkey[parentslot] = result.lastkey; + } else { + TLX_BTREE_PRINT("Forwarding lastkeyupdate: key " << result.lastkey); + myres |= result_t(btree_update_lastkey, result.lastkey); + } + } + + if (result.has(btree_fixmerge)) { + // either the current node or the next is empty and should be + // removed + if (inner->childid[slot]->slotuse != 0) slot++; + + // this is the child slot invalidated by the merge + TLX_BTREE_ASSERT(inner->childid[slot]->slotuse == 0); + + free_node(inner->childid[slot]); + + std::copy(inner->slotkey + slot, inner->slotkey + inner->slotuse, inner->slotkey + slot - 1); + std::copy(inner->childid + slot + 1, inner->childid + inner->slotuse + 1, inner->childid + slot); + + inner->slotuse--; + + if (inner->level == 1) { + // fix split key for children leaves + slot--; + LeafNode *child = static_cast(inner->childid[slot]); + inner->slotkey[slot] = child->key(child->slotuse - 1); + } + } + + if (inner->is_underflow() && !(inner == root_ && inner->slotuse >= 1)) { + // case: the inner node is the root and has just one + // child. that child becomes the new root + if (left_inner == nullptr && right_inner == nullptr) { + TLX_BTREE_ASSERT(inner == root_); + TLX_BTREE_ASSERT(inner->slotuse == 0); + + root_ = inner->childid[0]; + + inner->slotuse = 0; + free_node(inner); + + return btree_ok; + } + // case : if both left and right leaves would underflow in case + // of a shift, then merging is necessary. choose the more local + // merger with our parent + else if ((left_inner == nullptr || left_inner->is_few()) && (right_inner == nullptr || right_inner->is_few())) { + if (left_parent == parent) { + myres |= merge_inner(left_inner, inner, left_parent, parentslot - 1); + } else { + myres |= merge_inner(inner, right_inner, right_parent, parentslot); + } + } + // case : the right leaf has extra data, so balance right with + // current + else if ((left_inner != nullptr && left_inner->is_few()) && + (right_inner != nullptr && !right_inner->is_few())) { + if (right_parent == parent) { + shift_left_inner(inner, right_inner, right_parent, parentslot); + } else { + myres |= merge_inner(left_inner, inner, left_parent, parentslot - 1); + } + } + // case : the left leaf has extra data, so balance left with + // current + else if ((left_inner != nullptr && !left_inner->is_few()) && + (right_inner != nullptr && right_inner->is_few())) { + if (left_parent == parent) { + shift_right_inner(left_inner, inner, left_parent, parentslot - 1); + } else { + myres |= merge_inner(inner, right_inner, right_parent, parentslot); + } + } + // case : both the leaf and right leaves have extra data and our + // parent, choose the leaf with more data + else if (left_parent == right_parent) { + if (left_inner->slotuse <= right_inner->slotuse) { + shift_left_inner(inner, right_inner, right_parent, parentslot); + } else { + shift_right_inner(left_inner, inner, left_parent, parentslot - 1); + } + } else { + if (left_parent == parent) { + shift_right_inner(left_inner, inner, left_parent, parentslot - 1); + } else { + shift_left_inner(inner, right_inner, right_parent, parentslot); + } + } + } + + return myres; + } + } + + //! Merge two leaf nodes. The function moves all key/data pairs from right + //! to left and sets right's slotuse to zero. The right slot is then removed + //! by the calling parent node. + result_t merge_leaves(LeafNode *left, LeafNode *right, InnerNode *parent) { + TLX_BTREE_PRINT("Merge leaf nodes " << left << " and " << right << " with common parent " << parent << "."); + (void)parent; + + TLX_BTREE_ASSERT(left->is_leafnode() && right->is_leafnode()); + TLX_BTREE_ASSERT(parent->level == 1); + + TLX_BTREE_ASSERT(left->slotuse + right->slotuse < leaf_slotmax); + + std::copy(right->slotdata, right->slotdata + right->slotuse, left->slotdata + left->slotuse); + + left->slotuse += right->slotuse; + + left->next_leaf = right->next_leaf; + if (left->next_leaf) + left->next_leaf->prev_leaf = left; + else + tail_leaf_ = left; + + right->slotuse = 0; + + return btree_fixmerge; + } + + //! Merge two inner nodes. The function moves all key/childid pairs from + //! right to left and sets right's slotuse to zero. The right slot is then + //! removed by the calling parent node. + static result_t merge_inner(InnerNode *left, InnerNode *right, InnerNode *parent, unsigned int parentslot) { + TLX_BTREE_PRINT("Merge inner nodes " << left << " and " << right << " with common parent " << parent << "."); + + TLX_BTREE_ASSERT(left->level == right->level); + TLX_BTREE_ASSERT(parent->level == left->level + 1); + + TLX_BTREE_ASSERT(parent->childid[parentslot] == left); + + TLX_BTREE_ASSERT(left->slotuse + right->slotuse < inner_slotmax); + + if (self_verify) { + // find the left node's slot in the parent's children + unsigned int leftslot = 0; + while (leftslot <= parent->slotuse && parent->childid[leftslot] != left) ++leftslot; + + TLX_BTREE_ASSERT(leftslot < parent->slotuse); + TLX_BTREE_ASSERT(parent->childid[leftslot] == left); + TLX_BTREE_ASSERT(parent->childid[leftslot + 1] == right); + + TLX_BTREE_ASSERT(parentslot == leftslot); + } + + // retrieve the decision key from parent + left->slotkey[left->slotuse] = parent->slotkey[parentslot]; + left->slotuse++; + + // copy over keys and children from right + std::copy(right->slotkey, right->slotkey + right->slotuse, left->slotkey + left->slotuse); + std::copy(right->childid, right->childid + right->slotuse + 1, left->childid + left->slotuse); + + left->slotuse += right->slotuse; + right->slotuse = 0; + + return btree_fixmerge; + } + + //! Balance two leaf nodes. The function moves key/data pairs from right to + //! left so that both nodes are equally filled. The parent node is updated + //! if possible. + static result_t shift_left_leaf(LeafNode *left, LeafNode *right, InnerNode *parent, unsigned int parentslot) { + TLX_BTREE_ASSERT(left->is_leafnode() && right->is_leafnode()); + TLX_BTREE_ASSERT(parent->level == 1); + + TLX_BTREE_ASSERT(left->next_leaf == right); + TLX_BTREE_ASSERT(left == right->prev_leaf); + + TLX_BTREE_ASSERT(left->slotuse < right->slotuse); + TLX_BTREE_ASSERT(parent->childid[parentslot] == left); + + unsigned int shiftnum = (right->slotuse - left->slotuse) >> 1; + + TLX_BTREE_PRINT("Shifting (leaf) " << shiftnum << " entries to left " << left << " from right " << right + << " with common parent " << parent << "."); + + TLX_BTREE_ASSERT(left->slotuse + shiftnum < leaf_slotmax); + + // copy the first items from the right node to the last slot in the left + // node. + + std::copy(right->slotdata, right->slotdata + shiftnum, left->slotdata + left->slotuse); + + left->slotuse += shiftnum; + + // shift all slots in the right node to the left + + std::copy(right->slotdata + shiftnum, right->slotdata + right->slotuse, right->slotdata); + + right->slotuse -= shiftnum; + + // fixup parent + if (parentslot < parent->slotuse) { + parent->slotkey[parentslot] = left->key(left->slotuse - 1); + return btree_ok; + } else { // the update is further up the tree + return result_t(btree_update_lastkey, left->key(left->slotuse - 1)); + } + } + + //! Balance two inner nodes. The function moves key/data pairs from right to + //! left so that both nodes are equally filled. The parent node is updated + //! if possible. + static void shift_left_inner(InnerNode *left, InnerNode *right, InnerNode *parent, unsigned int parentslot) { + TLX_BTREE_ASSERT(left->level == right->level); + TLX_BTREE_ASSERT(parent->level == left->level + 1); + + TLX_BTREE_ASSERT(left->slotuse < right->slotuse); + TLX_BTREE_ASSERT(parent->childid[parentslot] == left); + + unsigned int shiftnum = (right->slotuse - left->slotuse) >> 1; + + TLX_BTREE_PRINT("Shifting (inner) " << shiftnum << " entries to left " << left << " from right " << right + << " with common parent " << parent << "."); + + TLX_BTREE_ASSERT(left->slotuse + shiftnum < inner_slotmax); + + if (self_verify) { + // find the left node's slot in the parent's children and compare to + // parentslot + + unsigned int leftslot = 0; + while (leftslot <= parent->slotuse && parent->childid[leftslot] != left) ++leftslot; + + TLX_BTREE_ASSERT(leftslot < parent->slotuse); + TLX_BTREE_ASSERT(parent->childid[leftslot] == left); + TLX_BTREE_ASSERT(parent->childid[leftslot + 1] == right); + + TLX_BTREE_ASSERT(leftslot == parentslot); + } + + // copy the parent's decision slotkey and childid to the first new key + // on the left + left->slotkey[left->slotuse] = parent->slotkey[parentslot]; + left->slotuse++; + + // copy the other items from the right node to the last slots in the + // left node. + std::copy(right->slotkey, right->slotkey + shiftnum - 1, left->slotkey + left->slotuse); + std::copy(right->childid, right->childid + shiftnum, left->childid + left->slotuse); + + left->slotuse += shiftnum - 1; + + // fixup parent + parent->slotkey[parentslot] = right->slotkey[shiftnum - 1]; + + // shift all slots in the right node + std::copy(right->slotkey + shiftnum, right->slotkey + right->slotuse, right->slotkey); + std::copy(right->childid + shiftnum, right->childid + right->slotuse + 1, right->childid); + + right->slotuse -= shiftnum; + } + + //! Balance two leaf nodes. The function moves key/data pairs from left to + //! right so that both nodes are equally filled. The parent node is updated + //! if possible. + static void shift_right_leaf(LeafNode *left, LeafNode *right, InnerNode *parent, unsigned int parentslot) { + TLX_BTREE_ASSERT(left->is_leafnode() && right->is_leafnode()); + TLX_BTREE_ASSERT(parent->level == 1); + + TLX_BTREE_ASSERT(left->next_leaf == right); + TLX_BTREE_ASSERT(left == right->prev_leaf); + TLX_BTREE_ASSERT(parent->childid[parentslot] == left); + + TLX_BTREE_ASSERT(left->slotuse > right->slotuse); + + unsigned int shiftnum = (left->slotuse - right->slotuse) >> 1; + + TLX_BTREE_PRINT("Shifting (leaf) " << shiftnum << " entries to right " << right << " from left " << left + << " with common parent " << parent << "."); + + if (self_verify) { + // find the left node's slot in the parent's children + unsigned int leftslot = 0; + while (leftslot <= parent->slotuse && parent->childid[leftslot] != left) ++leftslot; + + TLX_BTREE_ASSERT(leftslot < parent->slotuse); + TLX_BTREE_ASSERT(parent->childid[leftslot] == left); + TLX_BTREE_ASSERT(parent->childid[leftslot + 1] == right); + + TLX_BTREE_ASSERT(leftslot == parentslot); + } + + // shift all slots in the right node + + TLX_BTREE_ASSERT(right->slotuse + shiftnum < leaf_slotmax); + + std::copy_backward(right->slotdata, right->slotdata + right->slotuse, right->slotdata + right->slotuse + shiftnum); + + right->slotuse += shiftnum; + + // copy the last items from the left node to the first slot in the right + // node. + std::copy(left->slotdata + left->slotuse - shiftnum, left->slotdata + left->slotuse, right->slotdata); + + left->slotuse -= shiftnum; + + parent->slotkey[parentslot] = left->key(left->slotuse - 1); + } + + //! Balance two inner nodes. The function moves key/data pairs from left to + //! right so that both nodes are equally filled. The parent node is updated + //! if possible. + static void shift_right_inner(InnerNode *left, InnerNode *right, InnerNode *parent, unsigned int parentslot) { + TLX_BTREE_ASSERT(left->level == right->level); + TLX_BTREE_ASSERT(parent->level == left->level + 1); + + TLX_BTREE_ASSERT(left->slotuse > right->slotuse); + TLX_BTREE_ASSERT(parent->childid[parentslot] == left); + + unsigned int shiftnum = (left->slotuse - right->slotuse) >> 1; + + TLX_BTREE_PRINT("Shifting (leaf) " << shiftnum << " entries to right " << right << " from left " << left + << " with common parent " << parent << "."); + + if (self_verify) { + // find the left node's slot in the parent's children + unsigned int leftslot = 0; + while (leftslot <= parent->slotuse && parent->childid[leftslot] != left) ++leftslot; + + TLX_BTREE_ASSERT(leftslot < parent->slotuse); + TLX_BTREE_ASSERT(parent->childid[leftslot] == left); + TLX_BTREE_ASSERT(parent->childid[leftslot + 1] == right); + + TLX_BTREE_ASSERT(leftslot == parentslot); + } + + // shift all slots in the right node + + TLX_BTREE_ASSERT(right->slotuse + shiftnum < inner_slotmax); + + std::copy_backward(right->slotkey, right->slotkey + right->slotuse, right->slotkey + right->slotuse + shiftnum); + std::copy_backward(right->childid, right->childid + right->slotuse + 1, + right->childid + right->slotuse + 1 + shiftnum); + + right->slotuse += shiftnum; + + // copy the parent's decision slotkey and childid to the last new key on + // the right + right->slotkey[shiftnum - 1] = parent->slotkey[parentslot]; + + // copy the remaining last items from the left node to the first slot in + // the right node. + std::copy(left->slotkey + left->slotuse - shiftnum + 1, left->slotkey + left->slotuse, right->slotkey); + std::copy(left->childid + left->slotuse - shiftnum + 1, left->childid + left->slotuse + 1, right->childid); + + // copy the first to-be-removed key from the left node to the parent's + // decision slot + parent->slotkey[parentslot] = left->slotkey[left->slotuse - shiftnum]; + + left->slotuse -= shiftnum; + } + + //! \} + +#ifdef TLX_BTREE_DEBUG + + public: + //! \name Debug Printing + //! \{ + + //! Print out the B+ tree structure with keys onto the given ostream. This + //! function requires that the header is compiled with TLX_BTREE_DEBUG and + //! that key_type is printable via std::ostream. + void print(std::ostream &os) const { + if (root_) { + print_node(os, root_, 0, true); + } + } + + //! Print out only the leaves via the double linked list. + void print_leaves(std::ostream &os) const { + os << "leaves:" << std::endl; + + const LeafNode *n = head_leaf_; + + while (n) { + os << " " << n << std::endl; + + n = n->next_leaf; + } + } + + private: + //! Recursively descend down the tree and print out nodes. + static void print_node(std::ostream &os, const node *node, unsigned int depth = 0, bool recursive = false) { + for (unsigned int i = 0; i < depth; i++) os << " "; + + os << "node " << node << " level " << node->level << " slotuse " << node->slotuse << std::endl; + + if (node->is_leafnode()) { + const LeafNode *leafnode = static_cast(node); + + for (unsigned int i = 0; i < depth; i++) os << " "; + os << " leaf prev " << leafnode->prev_leaf << " next " << leafnode->next_leaf << std::endl; + + for (unsigned int i = 0; i < depth; i++) os << " "; + + for (unsigned short slot = 0; slot < leafnode->slotuse; ++slot) { + // os << leafnode->key(slot) << " " + // << "(data: " << leafnode->slotdata[slot] << ") "; + os << leafnode->key(slot) << " "; + } + os << std::endl; + } else { + const InnerNode *innernode = static_cast(node); + + for (unsigned int i = 0; i < depth; i++) os << " "; + + for (unsigned short slot = 0; slot < innernode->slotuse; ++slot) { + os << "(" << innernode->childid[slot] << ") " << innernode->slotkey[slot] << " "; + } + os << "(" << innernode->childid[innernode->slotuse] << ")" << std::endl; + + if (recursive) { + for (unsigned short slot = 0; slot < innernode->slotuse + 1; ++slot) { + print_node(os, innernode->childid[slot], depth + 1, recursive); + } + } + } + } + + //! \} +#endif + + public: + //! \name Verification of B+ Tree Invariants + //! \{ + + //! Run a thorough verification of all B+ tree invariants. The program + //! aborts via tlx_die_unless() if something is wrong. + void verify() const { + key_type minkey, maxkey; + tree_stats vstats; + + if (root_) { + verify_node(root_, &minkey, &maxkey, vstats); + + tlx_die_unless(vstats.size == stats_.size); + tlx_die_unless(vstats.leaves == stats_.leaves); + tlx_die_unless(vstats.inner_nodes == stats_.inner_nodes); + + verify_leaflinks(); + } + } + + private: + //! Recursively descend down the tree and verify each node + void verify_node(const node *n, key_type *minkey, key_type *maxkey, tree_stats &vstats) const { + TLX_BTREE_PRINT("verifynode " << n); + + if (n->is_leafnode()) { + const LeafNode *leaf = static_cast(n); + + tlx_die_unless(leaf == root_ || !leaf->is_underflow()); + tlx_die_unless(leaf->slotuse > 0); + + for (unsigned short slot = 0; slot < leaf->slotuse - 1; ++slot) { + tlx_die_unless(key_lessequal(leaf->key(slot), leaf->key(slot + 1))); + } + + *minkey = leaf->key(0); + *maxkey = leaf->key(leaf->slotuse - 1); + + vstats.leaves++; + vstats.size += leaf->slotuse; + } else // !n->is_leafnode() + { + const InnerNode *inner = static_cast(n); + vstats.inner_nodes++; + + tlx_die_unless(inner == root_ || !inner->is_underflow()); + tlx_die_unless(inner->slotuse > 0); + + for (unsigned short slot = 0; slot < inner->slotuse - 1; ++slot) { + tlx_die_unless(key_lessequal(inner->key(slot), inner->key(slot + 1))); + } + + for (unsigned short slot = 0; slot <= inner->slotuse; ++slot) { + const node *subnode = inner->childid[slot]; + key_type subminkey = key_type(); + key_type submaxkey = key_type(); + + tlx_die_unless(subnode->level + 1 == inner->level); + verify_node(subnode, &subminkey, &submaxkey, vstats); + + TLX_BTREE_PRINT("verify subnode " << subnode << ": " << subminkey << " - " << submaxkey); + + if (slot == 0) + *minkey = subminkey; + else + tlx_die_unless(key_greaterequal(subminkey, inner->key(slot - 1))); + + if (slot == inner->slotuse) + *maxkey = submaxkey; + else + tlx_die_unless(key_equal(inner->key(slot), submaxkey)); + + if (inner->level == 1 && slot < inner->slotuse) { + // children are leaves and must be linked together in the + // correct order + const LeafNode *leafa = static_cast(inner->childid[slot]); + const LeafNode *leafb = static_cast(inner->childid[slot + 1]); + + tlx_die_unless(leafa->next_leaf == leafb); + tlx_die_unless(leafa == leafb->prev_leaf); + } + if (inner->level == 2 && slot < inner->slotuse) { + // verify leaf links between the adjacent inner nodes + const InnerNode *parenta = static_cast(inner->childid[slot]); + const InnerNode *parentb = static_cast(inner->childid[slot + 1]); + + const LeafNode *leafa = static_cast(parenta->childid[parenta->slotuse]); + const LeafNode *leafb = static_cast(parentb->childid[0]); + + tlx_die_unless(leafa->next_leaf == leafb); + tlx_die_unless(leafa == leafb->prev_leaf); + } + } + } + } + + //! Verify the double linked list of leaves. + void verify_leaflinks() const { + const LeafNode *n = head_leaf_; + + tlx_die_unless(n->level == 0); + tlx_die_unless(!n || n->prev_leaf == nullptr); + + unsigned int testcount = 0; + + while (n) { + tlx_die_unless(n->level == 0); + tlx_die_unless(n->slotuse > 0); + + for (unsigned short slot = 0; slot < n->slotuse - 1; ++slot) { + tlx_die_unless(key_lessequal(n->key(slot), n->key(slot + 1))); + } + + testcount += n->slotuse; + + if (n->next_leaf) { + tlx_die_unless(key_lessequal(n->key(n->slotuse - 1), n->next_leaf->key(0))); + + tlx_die_unless(n == n->next_leaf->prev_leaf); + } else { + tlx_die_unless(tail_leaf_ == n); + } + + n = n->next_leaf; + } + + tlx_die_unless(testcount == size()); + } + + //! \} +}; + +//! \} +//! \} + +} // namespace tlx + +#endif // !TLX_CONTAINER_BTREE_HEADER + +/******************************************************************************/ diff --git a/tests/benchmark/btree_map.hpp b/tests/benchmark/btree_map.hpp new file mode 100644 index 000000000..3f718677b --- /dev/null +++ b/tests/benchmark/btree_map.hpp @@ -0,0 +1,475 @@ +/******************************************************************************* + * tlx/container/btree_map.hpp + * + * Part of tlx - http://panthema.net/tlx + * + * Copyright (C) 2008-2017 Timo Bingmann + * + * All rights reserved. Published under the Boost Software License, Version 1.0 + ******************************************************************************/ + +#ifndef TLX_CONTAINER_BTREE_MAP_HEADER +#define TLX_CONTAINER_BTREE_MAP_HEADER + +#include +#include +#include + +#include "btree.hpp" + +namespace tlx { + +//! \addtogroup tlx_container_btree +//! \{ + +/*! + * Specialized B+ tree template class implementing STL's map container. + * + * Implements the STL map using a B+ tree. It can be used as a drop-in + * replacement for std::map. Not all asymptotic time requirements are met in + * theory. The class has a traits class defining B+ tree properties like slots + * and self-verification. Furthermore an allocator can be specified for tree + * nodes. + */ +template , + typename Traits_ = btree_default_traits>, + typename Alloc_ = std::allocator>> +class btree_map { + public: + //! \name Template Parameter Types + //! \{ + + //! First template parameter: The key type of the btree. This is stored in + //! inner nodes. + typedef Key_ key_type; + + //! Second template parameter: The value type associated with each key. + //! Stored in the B+ tree's leaves + typedef Data_ data_type; + + //! Third template parameter: Key comparison function object + typedef Compare_ key_compare; + + //! Fourth template parameter: Traits object used to define more parameters + //! of the B+ tree + typedef Traits_ traits; + + //! Fifth template parameter: STL allocator + typedef Alloc_ allocator_type; + + //! \} + + // The macro TLX_BTREE_FRIENDS can be used by outside class to access the B+ + // tree internals. This was added for wxBTreeDemo to be able to draw the + // tree. + TLX_BTREE_FRIENDS; + + public: + //! \name Constructed Types + //! \{ + + //! Typedef of our own type + typedef btree_map self; + + //! Construct the STL-required value_type as a composition pair of key and + //! data types + typedef std::pair value_type; + + //! Key Extractor Struct + struct key_of_value { + //! pull first out of pair + static const key_type &get(const value_type &v) { return v.first; } + }; + + //! Implementation type of the btree_base + typedef BTree btree_impl; + + //! Function class comparing two value_type pairs. + typedef typename btree_impl::value_compare value_compare; + + //! Size type used to count keys + typedef typename btree_impl::size_type size_type; + + //! Small structure containing statistics about the tree + typedef typename btree_impl::tree_stats tree_stats; + + //! \} + + public: + //! \name Static Constant Options and Values of the B+ Tree + //! \{ + + //! Base B+ tree parameter: The number of key/data slots in each leaf + static const unsigned short leaf_slotmax = btree_impl::leaf_slotmax; + + //! Base B+ tree parameter: The number of key slots in each inner node, + //! this can differ from slots in each leaf. + static const unsigned short inner_slotmax = btree_impl::inner_slotmax; + + //! Computed B+ tree parameter: The minimum number of key/data slots used + //! in a leaf. If fewer slots are used, the leaf will be merged or slots + //! shifted from it's siblings. + static const unsigned short leaf_slotmin = btree_impl::leaf_slotmin; + + //! Computed B+ tree parameter: The minimum number of key slots used + //! in an inner node. If fewer slots are used, the inner node will be + //! merged or slots shifted from it's siblings. + static const unsigned short inner_slotmin = btree_impl::inner_slotmin; + + //! Debug parameter: Enables expensive and thorough checking of the B+ tree + //! invariants after each insert/erase operation. + static const bool self_verify = btree_impl::self_verify; + + //! Debug parameter: Prints out lots of debug information about how the + //! algorithms change the tree. Requires the header file to be compiled + //! with TLX_BTREE_DEBUG and the key type must be std::ostream printable. + static const bool debug = btree_impl::debug; + + //! Operational parameter: Allow duplicate keys in the btree. + static const bool allow_duplicates = btree_impl::allow_duplicates; + + //! \} + + public: + //! \name Iterators and Reverse Iterators + //! \{ + + //! STL-like iterator object for B+ tree items. The iterator points to a + //! specific slot number in a leaf. + typedef typename btree_impl::iterator iterator; + + //! STL-like iterator object for B+ tree items. The iterator points to a + //! specific slot number in a leaf. + typedef typename btree_impl::const_iterator const_iterator; + + //! create mutable reverse iterator by using STL magic + typedef typename btree_impl::reverse_iterator reverse_iterator; + + //! create constant reverse iterator by using STL magic + typedef typename btree_impl::const_reverse_iterator const_reverse_iterator; + + //! \} + + private: + //! \name Tree Implementation Object + //! \{ + + //! The contained implementation object + btree_impl tree_; + + //! \} + + public: + //! \name Constructors and Destructor + //! \{ + + //! Default constructor initializing an empty B+ tree with the standard key + //! comparison function + explicit btree_map(const allocator_type &alloc = allocator_type()) : tree_(alloc) {} + + //! Constructor initializing an empty B+ tree with a special key + //! comparison object + explicit btree_map(const key_compare &kcf, const allocator_type &alloc = allocator_type()) : tree_(kcf, alloc) {} + + //! Constructor initializing a B+ tree with the range [first,last) + template + btree_map(InputIterator first, InputIterator last, const allocator_type &alloc = allocator_type()) + : tree_(first, last, alloc) {} + + //! Constructor initializing a B+ tree with the range [first,last) and a + //! special key comparison object + template + btree_map(InputIterator first, InputIterator last, const key_compare &kcf, + const allocator_type &alloc = allocator_type()) + : tree_(first, last, kcf, alloc) {} + + //! Frees up all used B+ tree memory pages + ~btree_map() {} + + //! Fast swapping of two identical B+ tree objects. + void swap(btree_map &from) { std::swap(tree_, from.tree_); } + + //! \} + + public: + //! \name Key and Value Comparison Function Objects + //! \{ + + //! Constant access to the key comparison object sorting the B+ tree + key_compare key_comp() const { return tree_.key_comp(); } + + //! Constant access to a constructed value_type comparison object. required + //! by the STL + value_compare value_comp() const { return tree_.value_comp(); } + + //! \} + + public: + //! \name Allocators + //! \{ + + //! Return the base node allocator provided during construction. + allocator_type get_allocator() const { return tree_.get_allocator(); } + + //! \} + + public: + //! \name Fast Destruction of the B+ Tree + //! \{ + + //! Frees all key/data pairs and all nodes of the tree + void clear() { tree_.clear(); } + + //! \} + + public: + //! \name STL Iterator Construction Functions + //! \{ + + //! Constructs a read/data-write iterator that points to the first slot in + //! the first leaf of the B+ tree. + iterator begin() { return tree_.begin(); } + + //! Constructs a read/data-write iterator that points to the first invalid + //! slot in the last leaf of the B+ tree. + iterator end() { return tree_.end(); } + + //! Constructs a read-only constant iterator that points to the first slot + //! in the first leaf of the B+ tree. + const_iterator begin() const { return tree_.begin(); } + + //! Constructs a read-only constant iterator that points to the first + //! invalid slot in the last leaf of the B+ tree. + const_iterator end() const { return tree_.end(); } + + //! Constructs a read/data-write reverse iterator that points to the first + //! invalid slot in the last leaf of the B+ tree. Uses STL magic. + reverse_iterator rbegin() { return tree_.rbegin(); } + + //! Constructs a read/data-write reverse iterator that points to the first + //! slot in the first leaf of the B+ tree. Uses STL magic. + reverse_iterator rend() { return tree_.rend(); } + + //! Constructs a read-only reverse iterator that points to the first + //! invalid slot in the last leaf of the B+ tree. Uses STL magic. + const_reverse_iterator rbegin() const { return tree_.rbegin(); } + + //! Constructs a read-only reverse iterator that points to the first slot + //! in the first leaf of the B+ tree. Uses STL magic. + const_reverse_iterator rend() const { return tree_.rend(); } + + //! \} + + public: + //! \name Access Functions to the Item Count + //! \{ + + //! Return the number of key/data pairs in the B+ tree + size_type size() const { return tree_.size(); } + + //! Returns true if there is at least one key/data pair in the B+ tree + bool empty() const { return tree_.empty(); } + + //! Returns the largest possible size of the B+ Tree. This is just a + //! function required by the STL standard, the B+ Tree can hold more items. + size_type max_size() const { return tree_.max_size(); } + + //! Return a const reference to the current statistics. + const tree_stats &get_stats() const { return tree_.get_stats(); } + + //! \} + + public: + //! \name STL Access Functions Querying the Tree by Descending to a Leaf + //! \{ + + //! Non-STL function checking whether a key is in the B+ tree. The same as + //! (find(k) != end()) or (count() != 0). + bool exists(const key_type &key) const { return tree_.exists(key); } + + //! Tries to locate a key in the B+ tree and returns an iterator to the + //! key/data slot if found. If unsuccessful it returns end(). + iterator find(const key_type &key) { return tree_.find(key); } + + //! Tries to locate a key in the B+ tree and returns an constant iterator to + //! the key/data slot if found. If unsuccessful it returns end(). + const_iterator find(const key_type &key) const { return tree_.find(key); } + + //! Tries to locate a key in the B+ tree and returns the number of identical + //! key entries found. Since this is a unique map, count() returns either 0 + //! or 1. + size_type count(const key_type &key) const { return tree_.count(key); } + + //! Searches the B+ tree and returns an iterator to the first pair equal to + //! or greater than key, or end() if all keys are smaller. + iterator lower_bound(const key_type &key) { return tree_.lower_bound(key); } + + //! Searches the B+ tree and returns a constant iterator to the first pair + //! equal to or greater than key, or end() if all keys are smaller. + const_iterator lower_bound(const key_type &key) const { return tree_.lower_bound(key); } + + //! Searches the B+ tree and returns an iterator to the first pair greater + //! than key, or end() if all keys are smaller or equal. + iterator upper_bound(const key_type &key) { return tree_.upper_bound(key); } + + //! Searches the B+ tree and returns a constant iterator to the first pair + //! greater than key, or end() if all keys are smaller or equal. + const_iterator upper_bound(const key_type &key) const { return tree_.upper_bound(key); } + + //! Searches the B+ tree and returns both lower_bound() and upper_bound(). + std::pair equal_range(const key_type &key) { return tree_.equal_range(key); } + + //! Searches the B+ tree and returns both lower_bound() and upper_bound(). + std::pair equal_range(const key_type &key) const { return tree_.equal_range(key); } + + //! \} + + public: + //! \name B+ Tree Object Comparison Functions + //! \{ + + //! Equality relation of B+ trees of the same type. B+ trees of the same + //! size and equal elements (both key and data) are considered equal. + bool operator==(const btree_map &other) const { return (tree_ == other.tree_); } + + //! Inequality relation. Based on operator==. + bool operator!=(const btree_map &other) const { return (tree_ != other.tree_); } + + //! Total ordering relation of B+ trees of the same type. It uses + //! std::lexicographical_compare() for the actual comparison of elements. + bool operator<(const btree_map &other) const { return (tree_ < other.tree_); } + + //! Greater relation. Based on operator<. + bool operator>(const btree_map &other) const { return (tree_ > other.tree_); } + + //! Less-equal relation. Based on operator<. + bool operator<=(const btree_map &other) const { return (tree_ <= other.tree_); } + + //! Greater-equal relation. Based on operator<. + bool operator>=(const btree_map &other) const { return (tree_ >= other.tree_); } + + //! \} + + public: + //! \name Fast Copy: Assign Operator and Copy Constructors + //! \{ + + //! Assignment operator. All the key/data pairs are copied + btree_map &operator=(const btree_map &other) { + if (this != &other) tree_ = other.tree_; + return *this; + } + + //! Copy constructor. The newly initialized B+ tree object will contain a + //! copy of all key/data pairs. + btree_map(const btree_map &other) : tree_(other.tree_) {} + + //! \} + + public: + //! \name Public Insertion Functions + //! \{ + + //! Attempt to insert a key/data pair into the B+ tree. Fails if the pair is + //! already present. + std::pair insert(const value_type &x) { return tree_.insert(x); } + + //! Attempt to insert a key/data pair into the B+ tree. This function is the + //! same as the other insert. Fails if the inserted pair is already present. + std::pair insert2(const key_type &key, const data_type &data) { + return tree_.insert(value_type(key, data)); + } + + //! Attempt to insert a key/data pair into the B+ tree. The iterator hint is + //! currently ignored by the B+ tree insertion routine. + iterator insert(iterator hint, const value_type &x) { return tree_.insert(hint, x); } + + //! Attempt to insert a key/data pair into the B+ tree. The iterator hint is + //! currently ignored by the B+ tree insertion routine. + iterator insert2(iterator hint, const key_type &key, const data_type &data) { + return tree_.insert(hint, value_type(key, data)); + } + + //! Returns a reference to the object that is associated with a particular + //! key. If the map does not already contain such an object, operator[] + //! inserts the default object data_type(). + data_type &operator[](const key_type &key) { + iterator i = insert(value_type(key, data_type())).first; + return i->second; + } + + //! Attempt to insert the range [first,last) of value_type pairs into the B+ + //! tree. Each key/data pair is inserted individually. + template + void insert(InputIterator first, InputIterator last) { + return tree_.insert(first, last); + } + + //! Bulk load a sorted range [first,last). Loads items into leaves and + //! constructs a B-tree above them. The tree must be empty when calling this + //! function. + template + void bulk_load(Iterator first, Iterator last) { + return tree_.bulk_load(first, last); + } + + //! \} + + public: + //! \name Public Erase Functions + //! \{ + + //! Erases the key/data pairs associated with the given key. For this + //! unique-associative map there is no difference to erase(). + bool erase_one(const key_type &key) { return tree_.erase_one(key); } + + //! Erases all the key/data pairs associated with the given key. This is + //! implemented using erase_one(). + size_type erase(const key_type &key) { return tree_.erase(key); } + + //! Erase the key/data pair referenced by the iterator. + void erase(iterator iter) { return tree_.erase(iter); } + +#ifdef TLX_BTREE_TODO + //! Erase all key/data pairs in the range [first,last). This function is + //! currently not implemented by the B+ Tree. + void erase(iterator /* first */, iterator /* last */) { abort(); } +#endif + + //! \} + +#ifdef TLX_BTREE_DEBUG + + public: + //! \name Debug Printing + //! \{ + + //! Print out the B+ tree structure with keys onto the given ostream. This + //! function requires that the header is compiled with TLX_BTREE_DEBUG and + //! that key_type is printable via std::ostream. + void print(std::ostream &os) const { tree_.print(os); } + + //! Print out only the leaves via the double linked list. + void print_leaves(std::ostream &os) const { tree_.print_leaves(os); } + + //! \} +#endif + + public: + //! \name Verification of B+ Tree Invariants + //! \{ + + //! Run a thorough verification of all B+ tree invariants. The program + //! aborts via TLX_BTREE_ASSERT() if something is wrong. + void verify() const { tree_.verify(); } + + //! \} +}; + +//! \} + +} // namespace tlx + +#endif // !TLX_CONTAINER_BTREE_MAP_HEADER + +/******************************************************************************/ diff --git a/tests/benchmark/core.hpp b/tests/benchmark/core.hpp new file mode 100644 index 000000000..55e40bbed --- /dev/null +++ b/tests/benchmark/core.hpp @@ -0,0 +1,267 @@ +/******************************************************************************* + * tlx/die/core.hpp + * + * Part of tlx - http://panthema.net/tlx + * + * Copyright (C) 2016-2018 Timo Bingmann + * + * All rights reserved. Published under the Boost Software License, Version 1.0 + ******************************************************************************/ + +#ifndef TLX_DIE_CORE_HEADER +#define TLX_DIE_CORE_HEADER + +#include +#include +#include +#include +#include + +namespace tlx { + +/******************************************************************************/ +// die macros + +//! die with message - either throw an exception or die via std::terminate() +void die_with_message(const std::string &msg); + +//! die with message - either throw an exception or die via std::terminate() +void die_with_message(const char *msg, const char *file, size_t line); + +//! die with message - either throw an exception or die via std::terminate() +void die_with_message(const std::string &msg, const char *file, size_t line); + +//! Instead of std::terminate(), throw the output the message via an exception. +#define tlx_die_with_sstream(msg) \ + do { \ + std::ostringstream oss__; \ + oss__ << msg << " @ " << __FILE__ << ':' << __LINE__; \ + ::tlx::die_with_message(oss__.str()); \ + std::terminate(); /* tell compiler this never returns */ \ + } while (false) + +//! Instead of std::terminate(), throw the output the message via an exception. +#define tlx_die(msg) \ + do { \ + tlx_die_with_sstream("DIE: " << msg); \ + } while (false) + +//! Exception thrown by die_with_message() if +class DieException : public std::runtime_error { + public: + explicit DieException(const std::string &message); +}; + +//! Switch between dying via std::terminate() and throwing an exception. +//! Alternatively define the macro TLX_DIE_WITH_EXCEPTION=1 +bool set_die_with_exception(bool b); + +/******************************************************************************/ +// die_unless() and die_if() + +//! Check condition X and die miserably if false. Same as assert() except this +//! is also active in Release mode. +#define tlx_die_unless(X) \ + do { \ + if (!(X)) { \ + ::tlx::die_with_message("DIE: Assertion \"" #X "\" failed!", __FILE__, __LINE__); \ + } \ + } while (false) + +//! Check condition X and die miserably if true. Opposite of assert() except +//! this is also active in Release mode. +#define tlx_die_if(X) \ + do { \ + if (X) { \ + ::tlx::die_with_message("DIE: Assertion \"" #X "\" succeeded!", __FILE__, __LINE__); \ + } \ + } while (false) + +//! Check condition X and die miserably if false. Same as tlx_die_unless() +//! except the user additionally passes a message. +#define tlx_die_verbose_unless(X, msg) \ + do { \ + if (!(X)) { \ + tlx_die_with_sstream("DIE: Assertion \"" #X "\" failed!\n" << msg << '\n'); \ + } \ + } while (false) + +//! Check condition X and die miserably if false. Same as tlx_die_if() +//! except the user additionally passes a message. +#define tlx_die_verbose_if(X, msg) \ + do { \ + if ((X)) { \ + tlx_die_with_sstream("DIE: Assertion \"" #X "\" succeeded!\n" << msg << '\n'); \ + } \ + } while (false) + +/******************************************************************************/ +// die_unequal() + +//! helper method to compare two values in die_unequal() +template +inline bool die_equal_compare(TypeA a, TypeB b) { + return a == b; +} + +template <> +inline bool die_equal_compare(const char *a, const char *b) { + // compare string contents + return std::strcmp(a, b) == 0; +} + +template <> +inline bool die_equal_compare(float a, float b) { + // special case for NAN + return a != a ? b != b : a == b; +} + +template <> +inline bool die_equal_compare(double a, double b) { + // special case for NAN + return a != a ? b != b : a == b; +} + +//! Check that X == Y or die miserably, but output the values of X and Y for +//! better debugging. +#define tlx_die_unequal(X, Y) \ + do { \ + auto x__ = (X); /* NOLINT */ \ + auto y__ = (Y); /* NOLINT */ \ + if (!::tlx::die_equal_compare(x__, y__)) \ + tlx_die_with_sstream("DIE-UNEQUAL: " #X " != " #Y \ + " : " \ + "\"" \ + << x__ << "\" != \"" << y__ << "\""); \ + } while (false) + +//! Check that X == Y or die miserably, but output the values of X and Y for +//! better debugging. Only active if NDEBUG is not defined. +#ifdef NDEBUG +#define tlx_assert_equal(X, Y) +#else +#define tlx_assert_equal(X, Y) die_unequal(X, Y) +#endif + +//! Check that X == Y or die miserably, but output the values of X and Y for +//! better debugging. Same as tlx_die_unequal() except the user additionally +//! pass a message. +#define tlx_die_verbose_unequal(X, Y, msg) \ + do { \ + auto x__ = (X); /* NOLINT */ \ + auto y__ = (Y); /* NOLINT */ \ + if (!::tlx::die_equal_compare(x__, y__)) \ + tlx_die_with_sstream("DIE-UNEQUAL: " #X " != " #Y \ + " : " \ + "\"" \ + << x__ << "\" != \"" << y__ << "\"\n" \ + << msg << '\n'); \ + } while (false) + +/******************************************************************************/ +// die_unequal_eps() + +//! simple replacement for std::abs +template +inline Type die_unequal_eps_abs(const Type &t) { + return t < 0 ? -t : t; +} + +//! helper method to compare two values in die_unequal_eps() +template +inline bool die_equal_eps_compare(TypeA x, TypeB y, double eps) { + // special case for NAN + return x != x ? y != y : die_unequal_eps_abs(x - y) <= eps; +} + +//! Check that ABS(X - Y) <= eps or die miserably, but output the values of X +//! and Y for better debugging. +#define tlx_die_unequal_eps(X, Y, eps) \ + do { \ + auto x__ = (X); /* NOLINT */ \ + auto y__ = (Y); /* NOLINT */ \ + if (!::tlx::die_equal_eps_compare(x__, y__, eps)) \ + tlx_die("DIE-UNEQUAL-EPS: " #X " != " #Y " : " << std::setprecision(18) << "\"" << x__ << "\" != \"" << y__ \ + << "\""); \ + } while (false) + +//! Check that ABS(X - Y) <= eps or die miserably, but output the values of X +//! and Y for better debugging. Same as tlx_die_unequal_eps() except the user +//! additionally passes a message. +#define tlx_die_verbose_unequal_eps(X, Y, eps, msg) \ + do { \ + auto x__ = (X); /* NOLINT */ \ + auto y__ = (Y); /* NOLINT */ \ + if (!::tlx::die_equal_eps_compare(x__, y__, eps)) \ + tlx_die("DIE-UNEQUAL-EPS: " #X " != " #Y " : " << std::setprecision(18) << "\"" << x__ << "\" != \"" << y__ \ + << "\"\n" \ + << msg << '\n'); \ + } while (false) + +//! Check that ABS(X - Y) <= 0.000001 or die miserably, but output the values of +//! X and Y for better debugging. +#define tlx_die_unequal_eps6(X, Y) die_unequal_eps(X, Y, 1e-6) + +//! Check that ABS(X - Y) <= 0.000001 or die miserably, but output the values of +//! X and Y for better debugging. Same as tlx_die_unequal_eps6() except the user +//! additionally passes a message. +#define tlx_die_verbose_unequal_eps6(X, Y, msg) die_verbose_unequal_eps(X, Y, 1e-6, msg) + +/******************************************************************************/ +// die_equal() + +//! Die miserably if X == Y, but first output the values of X and Y for better +//! debugging. +#define tlx_die_equal(X, Y) \ + do { \ + auto x__ = (X); /* NOLINT */ \ + auto y__ = (Y); /* NOLINT */ \ + if (::tlx::die_equal_compare(x__, y__)) \ + tlx_die_with_sstream("DIE-EQUAL: " #X " == " #Y \ + " : " \ + "\"" \ + << x__ << "\" == \"" << y__ << "\""); \ + } while (false) + +//! Die miserably if X == Y, but first output the values of X and Y for better +//! debugging. Only active if NDEBUG is not defined. +#ifdef NDEBUG +#define tlx_assert_unequal(X, Y) +#else +#define tlx_assert_unequal(X, Y) die_equal(X, Y) +#endif + +//! Die miserably if X == Y, but first output the values of X and Y for better +//! debugging. Same as tlx_die_equal() except the user additionally passes a +//! message. +#define tlx_die_verbose_equal(X, Y, msg) \ + do { \ + auto x__ = (X); /* NOLINT */ \ + auto y__ = (Y); /* NOLINT */ \ + if (::tlx::die_equal_compare(x__, y__)) \ + tlx_die_with_sstream("DIE-EQUAL: " #X " == " #Y \ + " : " \ + "\"" \ + << x__ << "\" == \"" << y__ << "\"\n" \ + << msg << '\n'); \ + } while (false) + +/******************************************************************************/ +// die_unless_throws() + +//! Define to check that [code] throws and exception of given type +#define tlx_die_unless_throws(code, exception_type) \ + do { \ + try { \ + code; \ + } catch (const exception_type &) { \ + break; \ + } \ + ::tlx::die_with_message("DIE-UNLESS-THROWS: " #code " - NO EXCEPTION " #exception_type, __FILE__, __LINE__); \ + } while (false) + +} // namespace tlx + +#endif // !TLX_DIE_CORE_HEADER + +/******************************************************************************/ From 243fa5e4b26dbd21fa347a8566556eb5c19b57c2 Mon Sep 17 00:00:00 2001 From: jbajic Date: Sun, 27 Nov 2022 21:21:55 +0100 Subject: [PATCH 03/16] Add insert benchmark --- src/storage/v3/key_store.hpp | 5 +- src/storage/v3/property_store.cpp | 10 ++ src/storage/v3/property_store.hpp | 4 +- src/storage/v3/vertex.hpp | 2 + tests/benchmark/CMakeLists.txt | 13 +- tests/benchmark/data_structures_insert.cpp | 113 ++++++++++++++++++ ...uctures.cpp => data_structures_random.cpp} | 0 7 files changed, 141 insertions(+), 6 deletions(-) create mode 100644 tests/benchmark/data_structures_insert.cpp rename tests/benchmark/{data_structures.cpp => data_structures_random.cpp} (100%) diff --git a/src/storage/v3/key_store.hpp b/src/storage/v3/key_store.hpp index 4bc3c25e3..1762273fb 100644 --- a/src/storage/v3/key_store.hpp +++ b/src/storage/v3/key_store.hpp @@ -26,11 +26,12 @@ using PrimaryKey = std::vector; class KeyStore { public: + KeyStore() = default; explicit KeyStore(const PrimaryKey &key_values); - KeyStore(const KeyStore &) = delete; + KeyStore(const KeyStore &) = default; KeyStore(KeyStore &&other) noexcept = default; - KeyStore &operator=(const KeyStore &) = delete; + KeyStore &operator=(const KeyStore &) = default; KeyStore &operator=(KeyStore &&other) noexcept = default; ~KeyStore() = default; diff --git a/src/storage/v3/property_store.cpp b/src/storage/v3/property_store.cpp index fc7bd6984..bf8460003 100644 --- a/src/storage/v3/property_store.cpp +++ b/src/storage/v3/property_store.cpp @@ -928,6 +928,16 @@ void SetSizeData(uint8_t *buffer, uint64_t size, uint8_t *data) { PropertyStore::PropertyStore() { memset(buffer_, 0, sizeof(buffer_)); } +PropertyStore::PropertyStore(const PropertyStore &other) { memcpy(buffer_, other.buffer_, sizeof(buffer_)); } + +PropertyStore &PropertyStore::operator=(const PropertyStore &other) { + if (this == &other) { + return *this; + } + memcpy(buffer_, other.buffer_, sizeof(buffer_)); + return *this; +} + PropertyStore::PropertyStore(PropertyStore &&other) noexcept { memcpy(buffer_, other.buffer_, sizeof(buffer_)); memset(other.buffer_, 0, sizeof(other.buffer_)); diff --git a/src/storage/v3/property_store.hpp b/src/storage/v3/property_store.hpp index 477795203..00dc4d40f 100644 --- a/src/storage/v3/property_store.hpp +++ b/src/storage/v3/property_store.hpp @@ -25,9 +25,9 @@ class PropertyStore { public: PropertyStore(); - PropertyStore(const PropertyStore &) = delete; + PropertyStore(const PropertyStore &); PropertyStore(PropertyStore &&other) noexcept; - PropertyStore &operator=(const PropertyStore &) = delete; + PropertyStore &operator=(const PropertyStore &); PropertyStore &operator=(PropertyStore &&other) noexcept; ~PropertyStore(); diff --git a/src/storage/v3/vertex.hpp b/src/storage/v3/vertex.hpp index 2ed4c6e4c..cbfac4ee8 100644 --- a/src/storage/v3/vertex.hpp +++ b/src/storage/v3/vertex.hpp @@ -31,6 +31,8 @@ namespace memgraph::storage::v3 { struct Vertex { using EdgeLink = std::tuple; + Vertex() = default; + Vertex(Delta *delta, const std::vector &primary_properties) : keys{primary_properties}, delta{delta} { MG_ASSERT(delta == nullptr || delta->action == Delta::Action::DELETE_OBJECT, "Vertex must be created with an initial DELETE_OBJECT delta!"); diff --git a/tests/benchmark/CMakeLists.txt b/tests/benchmark/CMakeLists.txt index 46905a4d8..bf32e3071 100644 --- a/tests/benchmark/CMakeLists.txt +++ b/tests/benchmark/CMakeLists.txt @@ -68,5 +68,14 @@ target_link_libraries(${test_prefix}storage_v2_property_store mg-storage-v2) add_benchmark(future.cpp) target_link_libraries(${test_prefix}future mg-io) -add_benchmark(data_structures.cpp) -target_link_libraries(${test_prefix}data_structures mg-utils) +add_benchmark(data_structures_random.cpp) +target_link_libraries(${test_prefix}data_structures_random mg-utils) + +add_benchmark(data_structures_insert.cpp) +target_link_libraries(${test_prefix}data_structures_insert mg-utils mg-storage-v3) + +add_benchmark(data_structures_find.cpp) +target_link_libraries(${test_prefix}data_structures_find mg-utils mg-storage-v3) + +add_benchmark(data_structures_contains.cpp) +target_link_libraries(${test_prefix}data_structures_contains mg-utils mg-storage-v3) diff --git a/tests/benchmark/data_structures_insert.cpp b/tests/benchmark/data_structures_insert.cpp new file mode 100644 index 000000000..be2d7457f --- /dev/null +++ b/tests/benchmark/data_structures_insert.cpp @@ -0,0 +1,113 @@ +// Copyright 2022 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "btree_map.hpp" +#include "skip_list_common.hpp" +#include "storage/v3/key_store.hpp" +#include "storage/v3/lexicographically_ordered_vertex.hpp" +#include "storage/v3/mvcc.hpp" +#include "storage/v3/property_value.hpp" +#include "storage/v3/transaction.hpp" +#include "storage/v3/vertex.hpp" +#include "utils/skip_list.hpp" + +namespace memgraph::benchmark { + +/////////////////////////////////////////////////////////////////////////////// +// Testing Insert Operation +/////////////////////////////////////////////////////////////////////////////// +static void BM_BenchmarkInsertSkipList(::benchmark::State &state) { + utils::SkipList skip_list; + coordinator::Hlc start_timestamp; + storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; + storage::v3::Transaction transaction{start_timestamp, isolation_level}; + auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); + + for (auto _ : state) { + for (auto i{0}; i < state.range(0); ++i) { + auto acc = skip_list.access(); + acc.insert({storage::v3::Vertex(delta, std::vector{storage::v3::PropertyValue{i}})}); + } + } +} + +static void BM_BenchmarkInsertStdMap(::benchmark::State &state) { + std::map std_map; + coordinator::Hlc start_timestamp; + storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; + storage::v3::Transaction transaction{start_timestamp, isolation_level}; + auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); + + for (auto _ : state) { + for (auto i{0}; i < state.range(0); ++i) { + std_map.insert({storage::v3::PrimaryKey{storage::v3::PropertyValue{i}}, + storage::v3::LexicographicallyOrderedVertex{storage::v3::Vertex{ + delta, std::vector{storage::v3::PropertyValue{i}}}}}); + } + } +} + +static void BM_BenchmarkInsertStdSet(::benchmark::State &state) { + std::set std_set; + coordinator::Hlc start_timestamp; + storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; + storage::v3::Transaction transaction{start_timestamp, isolation_level}; + auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); + + for (auto _ : state) { + for (auto i{0}; i < state.range(0); ++i) { + std_set.insert(storage::v3::LexicographicallyOrderedVertex{ + storage::v3::Vertex{delta, std::vector{storage::v3::PropertyValue{i}}}}); + } + } +} + +static void BM_BenchmarkInsertBppTree(::benchmark::State &state) { + tlx::btree_map bpp_tree; + coordinator::Hlc start_timestamp; + storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; + storage::v3::Transaction transaction{start_timestamp, isolation_level}; + auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); + + for (auto _ : state) { + for (auto i{0}; i < state.range(0); ++i) { + bpp_tree.insert({storage::v3::PrimaryKey{storage::v3::PropertyValue{i}}, + storage::v3::LexicographicallyOrderedVertex{storage::v3::Vertex{ + delta, std::vector{storage::v3::PropertyValue{i}}}}}); + } + } +} + +BENCHMARK(BM_BenchmarkInsertSkipList)->Arg(1000); + +BENCHMARK(BM_BenchmarkInsertStdMap)->Arg(1000); + +BENCHMARK(BM_BenchmarkInsertStdSet)->Arg(1000); + +BENCHMARK(BM_BenchmarkInsertBppTree)->Arg(1000); + +} // namespace memgraph::benchmark + +BENCHMARK_MAIN(); diff --git a/tests/benchmark/data_structures.cpp b/tests/benchmark/data_structures_random.cpp similarity index 100% rename from tests/benchmark/data_structures.cpp rename to tests/benchmark/data_structures_random.cpp From cceab46a7caca21cb534503cd92961da62c4f00f Mon Sep 17 00:00:00 2001 From: jbajic Date: Sun, 27 Nov 2022 21:59:18 +0100 Subject: [PATCH 04/16] Add find benchmark --- tests/benchmark/data_structures_find.cpp | 155 +++++++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 tests/benchmark/data_structures_find.cpp diff --git a/tests/benchmark/data_structures_find.cpp b/tests/benchmark/data_structures_find.cpp new file mode 100644 index 000000000..c2b2de83c --- /dev/null +++ b/tests/benchmark/data_structures_find.cpp @@ -0,0 +1,155 @@ +// Copyright 2022 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "btree_map.hpp" +#include "skip_list_common.hpp" +#include "storage/v3/key_store.hpp" +#include "storage/v3/lexicographically_ordered_vertex.hpp" +#include "storage/v3/mvcc.hpp" +#include "storage/v3/property_value.hpp" +#include "storage/v3/transaction.hpp" +#include "storage/v3/vertex.hpp" +#include "utils/skip_list.hpp" + +namespace memgraph::benchmark { + +/////////////////////////////////////////////////////////////////////////////// +// Testing Find Operation +/////////////////////////////////////////////////////////////////////////////// +static void BM_BenchmarkFindSkipList(::benchmark::State &state) { + utils::SkipList skip_list; + coordinator::Hlc start_timestamp; + storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; + storage::v3::Transaction transaction{start_timestamp, isolation_level}; + auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); + for (auto i{0}; i < state.range(0); ++i) { + auto acc = skip_list.access(); + acc.insert({storage::v3::Vertex(delta, std::vector{storage::v3::PropertyValue{i}})}); + } + // So we can also have elements that does don't exist + std::mt19937 i_generator(std::random_device{}()); + std::uniform_int_distribution i_distribution(0, state.range(0) * 2); + int64_t found_elems{0}; + for (auto _ : state) { + for (auto i{0}; i < state.range(0); ++i) { + int64_t value = i_distribution(i_generator); + auto acc = skip_list.access(); + if (acc.find(storage::v3::PrimaryKey{{storage::v3::PropertyValue(value)}}) != acc.end()) { + found_elems++; + } + } + } + state.SetItemsProcessed(found_elems); +} + +static void BM_BenchmarkFindStdMap(::benchmark::State &state) { + std::map std_map; + coordinator::Hlc start_timestamp; + storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; + storage::v3::Transaction transaction{start_timestamp, isolation_level}; + auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); + for (auto i{0}; i < state.range(0); ++i) { + std_map.insert({storage::v3::PrimaryKey{storage::v3::PropertyValue{i}}, + storage::v3::LexicographicallyOrderedVertex{storage::v3::Vertex{ + delta, std::vector{storage::v3::PropertyValue{i}}}}}); + } + // So we can also have elements that does don't exist + std::mt19937 i_generator(std::random_device{}()); + std::uniform_int_distribution i_distribution(0, state.range(0) * 2); + int64_t found_elems{0}; + for (auto _ : state) { + for (auto i{0}; i < state.range(0); ++i) { + int64_t value = i_distribution(i_generator); + if (std_map.find(storage::v3::PrimaryKey{{storage::v3::PropertyValue(value)}}) != std_map.end()) { + found_elems++; + } + } + } + state.SetItemsProcessed(found_elems); +} + +static void BM_BenchmarkFindStdSet(::benchmark::State &state) { + std::set std_set; + coordinator::Hlc start_timestamp; + storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; + storage::v3::Transaction transaction{start_timestamp, isolation_level}; + auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); + for (auto i{0}; i < state.range(0); ++i) { + std_set.insert(storage::v3::LexicographicallyOrderedVertex{ + storage::v3::Vertex{delta, std::vector{storage::v3::PropertyValue{i}}}}); + } + // So we can also have elements that does don't exist + std::mt19937 i_generator(std::random_device{}()); + std::uniform_int_distribution i_distribution(0, state.range(0) * 2); + int64_t found_elems{0}; + for (auto _ : state) { + for (auto i{0}; i < state.range(0); ++i) { + int64_t value = i_distribution(i_generator); + if (std_set.find(storage::v3::LexicographicallyOrderedVertex{storage::v3::Vertex{ + delta, storage::v3::PrimaryKey{storage::v3::PropertyValue{value}}}}) != std_set.end()) { + found_elems++; + } + } + } + state.SetItemsProcessed(found_elems); +} + +static void BM_BenchmarkFindBppTree(::benchmark::State &state) { + tlx::btree_map bpp_tree; + coordinator::Hlc start_timestamp; + storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; + storage::v3::Transaction transaction{start_timestamp, isolation_level}; + auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); + for (auto i{0}; i < state.range(0); ++i) { + bpp_tree.insert({storage::v3::PrimaryKey{storage::v3::PropertyValue{i}}, + storage::v3::LexicographicallyOrderedVertex{storage::v3::Vertex{ + delta, std::vector{storage::v3::PropertyValue{i}}}}}); + } + // So we can also have elements that does don't exist + std::mt19937 i_generator(std::random_device{}()); + std::uniform_int_distribution i_distribution(0, state.range(0) * 2); + int64_t found_elems{0}; + for (auto _ : state) { + for (auto i{0}; i < state.range(0); ++i) { + int64_t value = i_distribution(i_generator); + if (bpp_tree.find(storage::v3::PrimaryKey{{storage::v3::PropertyValue(value)}}) != bpp_tree.end()) { + found_elems++; + } + } + } + state.SetItemsProcessed(found_elems); +} + +BENCHMARK(BM_BenchmarkFindSkipList)->Arg(1000); + +BENCHMARK(BM_BenchmarkFindStdMap)->Arg(1000); + +BENCHMARK(BM_BenchmarkFindStdSet)->Arg(1000); + +BENCHMARK(BM_BenchmarkFindBppTree)->Arg(1000); + +} // namespace memgraph::benchmark + +BENCHMARK_MAIN(); From 41b06a0a37e13343895e9934458203f1b6c56827 Mon Sep 17 00:00:00 2001 From: jbajic Date: Sun, 27 Nov 2022 22:28:23 +0100 Subject: [PATCH 05/16] Extract common functionalities --- tests/benchmark/CMakeLists.txt | 3 + tests/benchmark/data_structures_common.hpp | 76 +++++++++++ tests/benchmark/data_structures_contains.cpp | 131 +++++++++++++++++++ tests/benchmark/data_structures_find.cpp | 37 +----- tests/benchmark/data_structures_insert.cpp | 1 - 5 files changed, 216 insertions(+), 32 deletions(-) create mode 100644 tests/benchmark/data_structures_common.hpp create mode 100644 tests/benchmark/data_structures_contains.cpp diff --git a/tests/benchmark/CMakeLists.txt b/tests/benchmark/CMakeLists.txt index bf32e3071..6ec3fa644 100644 --- a/tests/benchmark/CMakeLists.txt +++ b/tests/benchmark/CMakeLists.txt @@ -79,3 +79,6 @@ target_link_libraries(${test_prefix}data_structures_find mg-utils mg-storage-v3) add_benchmark(data_structures_contains.cpp) target_link_libraries(${test_prefix}data_structures_contains mg-utils mg-storage-v3) + +add_benchmark(data_structures_remove.cpp) +target_link_libraries(${test_prefix}data_structures_contains mg-utils mg-storage-v3) diff --git a/tests/benchmark/data_structures_common.hpp b/tests/benchmark/data_structures_common.hpp new file mode 100644 index 000000000..fe6129a22 --- /dev/null +++ b/tests/benchmark/data_structures_common.hpp @@ -0,0 +1,76 @@ +// Copyright 2022 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#pragma once + +#include +#include +#include + +#include "btree_map.hpp" +#include "coordinator/hybrid_logical_clock.hpp" +#include "storage/v3/lexicographically_ordered_vertex.hpp" +#include "storage/v3/mvcc.hpp" +#include "storage/v3/transaction.hpp" +#include "utils/skip_list.hpp" + +namespace memgraph::benchmark { + +template +inline void PrepareData(utils::SkipList &skip_list, const int64_t num_elements) { + coordinator::Hlc start_timestamp; + storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; + storage::v3::Transaction transaction{start_timestamp, isolation_level}; + auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); + for (auto i{0}; i < num_elements; ++i) { + auto acc = skip_list.access(); + acc.insert({storage::v3::Vertex(delta, std::vector{storage::v3::PropertyValue{i}})}); + } +} + +template +inline void PrepareData(std::map &std_map, const int64_t num_elements) { + coordinator::Hlc start_timestamp; + storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; + storage::v3::Transaction transaction{start_timestamp, isolation_level}; + auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); + for (auto i{0}; i < num_elements; ++i) { + std_map.insert({storage::v3::PrimaryKey{storage::v3::PropertyValue{i}}, + storage::v3::LexicographicallyOrderedVertex{storage::v3::Vertex{ + delta, std::vector{storage::v3::PropertyValue{i}}}}}); + } +} + +template +inline void PrepareData(std::set &std_set, const int64_t num_elements) { + coordinator::Hlc start_timestamp; + storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; + storage::v3::Transaction transaction{start_timestamp, isolation_level}; + auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); + for (auto i{0}; i < num_elements; ++i) { + std_set.insert(storage::v3::LexicographicallyOrderedVertex{ + storage::v3::Vertex{delta, std::vector{storage::v3::PropertyValue{i}}}}); + } +} + +template +inline void PrepareData(tlx::btree_map &bpp_tree, const int64_t num_elements) { + coordinator::Hlc start_timestamp; + storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; + storage::v3::Transaction transaction{start_timestamp, isolation_level}; + auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); + for (auto i{0}; i < num_elements; ++i) { + bpp_tree.insert({storage::v3::PrimaryKey{storage::v3::PropertyValue{i}}, + storage::v3::LexicographicallyOrderedVertex{storage::v3::Vertex{ + delta, std::vector{storage::v3::PropertyValue{i}}}}}); + } +} +} // namespace memgraph::benchmark diff --git a/tests/benchmark/data_structures_contains.cpp b/tests/benchmark/data_structures_contains.cpp new file mode 100644 index 000000000..08596c7a7 --- /dev/null +++ b/tests/benchmark/data_structures_contains.cpp @@ -0,0 +1,131 @@ +// Copyright 2022 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "btree_map.hpp" +#include "data_structures_common.hpp" +#include "storage/v3/key_store.hpp" +#include "storage/v3/lexicographically_ordered_vertex.hpp" +#include "storage/v3/mvcc.hpp" +#include "storage/v3/property_value.hpp" +#include "storage/v3/transaction.hpp" +#include "storage/v3/vertex.hpp" +#include "utils/skip_list.hpp" + +namespace memgraph::benchmark { + +/////////////////////////////////////////////////////////////////////////////// +// Testing Contains Operation +/////////////////////////////////////////////////////////////////////////////// +static void BM_BenchmarkContainsSkipList(::benchmark::State &state) { + utils::SkipList skip_list; + PrepareData(skip_list, state.range(0)); + // So we can also have elements that does don't exist + std::mt19937 i_generator(std::random_device{}()); + std::uniform_int_distribution i_distribution(0, state.range(0) * 2); + int64_t found_elems{0}; + for (auto _ : state) { + for (auto i{0}; i < state.range(0); ++i) { + int64_t value = i_distribution(i_generator); + auto acc = skip_list.access(); + if (acc.contains(storage::v3::PrimaryKey{{storage::v3::PropertyValue(value)}})) { + found_elems++; + } + } + } + state.SetItemsProcessed(found_elems); +} + +static void BM_BenchmarkContainsStdMap(::benchmark::State &state) { + std::map std_map; + PrepareData(std_map, state.range(0)); + + // So we can also have elements that does don't exist + std::mt19937 i_generator(std::random_device{}()); + std::uniform_int_distribution i_distribution(0, state.range(0) * 2); + int64_t found_elems{0}; + for (auto _ : state) { + for (auto i{0}; i < state.range(0); ++i) { + int64_t value = i_distribution(i_generator); + if (std_map.contains(storage::v3::PrimaryKey{{storage::v3::PropertyValue(value)}})) { + found_elems++; + } + } + } + state.SetItemsProcessed(found_elems); +} + +static void BM_BenchmarkContainsStdSet(::benchmark::State &state) { + std::set std_set; + PrepareData(std_set, state.range(0)); + coordinator::Hlc start_timestamp; + storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; + storage::v3::Transaction transaction{start_timestamp, isolation_level}; + auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); + // So we can also have elements that does don't exist + std::mt19937 i_generator(std::random_device{}()); + std::uniform_int_distribution i_distribution(0, state.range(0) * 2); + int64_t found_elems{0}; + for (auto _ : state) { + for (auto i{0}; i < state.range(0); ++i) { + int64_t value = i_distribution(i_generator); + if (std_set.contains(storage::v3::LexicographicallyOrderedVertex{ + storage::v3::Vertex{delta, storage::v3::PrimaryKey{storage::v3::PropertyValue{value}}}})) { + found_elems++; + } + } + } + state.SetItemsProcessed(found_elems); +} + +static void BM_BenchmarkContainsBppTree(::benchmark::State &state) { + tlx::btree_map bpp_tree; + PrepareData(bpp_tree, state.range(0)); + + // So we can also have elements that does don't exist + std::mt19937 i_generator(std::random_device{}()); + std::uniform_int_distribution i_distribution(0, state.range(0) * 2); + int64_t found_elems{0}; + for (auto _ : state) { + for (auto i{0}; i < state.range(0); ++i) { + int64_t value = i_distribution(i_generator); + if (bpp_tree.count(storage::v3::PrimaryKey{{storage::v3::PropertyValue(value)}}) > 0) { + found_elems++; + } + } + } + state.SetItemsProcessed(found_elems); +} + +BENCHMARK(BM_BenchmarkContainsSkipList)->Arg(1000); + +BENCHMARK(BM_BenchmarkContainsStdMap)->Arg(1000); + +BENCHMARK(BM_BenchmarkContainsStdSet)->Arg(1000); + +BENCHMARK(BM_BenchmarkContainsBppTree)->Arg(1000); + +} // namespace memgraph::benchmark + +BENCHMARK_MAIN(); diff --git a/tests/benchmark/data_structures_find.cpp b/tests/benchmark/data_structures_find.cpp index c2b2de83c..042066d68 100644 --- a/tests/benchmark/data_structures_find.cpp +++ b/tests/benchmark/data_structures_find.cpp @@ -24,7 +24,7 @@ #include #include "btree_map.hpp" -#include "skip_list_common.hpp" +#include "data_structures_common.hpp" #include "storage/v3/key_store.hpp" #include "storage/v3/lexicographically_ordered_vertex.hpp" #include "storage/v3/mvcc.hpp" @@ -40,14 +40,7 @@ namespace memgraph::benchmark { /////////////////////////////////////////////////////////////////////////////// static void BM_BenchmarkFindSkipList(::benchmark::State &state) { utils::SkipList skip_list; - coordinator::Hlc start_timestamp; - storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; - storage::v3::Transaction transaction{start_timestamp, isolation_level}; - auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); - for (auto i{0}; i < state.range(0); ++i) { - auto acc = skip_list.access(); - acc.insert({storage::v3::Vertex(delta, std::vector{storage::v3::PropertyValue{i}})}); - } + PrepareData(skip_list, state.range(0)); // So we can also have elements that does don't exist std::mt19937 i_generator(std::random_device{}()); std::uniform_int_distribution i_distribution(0, state.range(0) * 2); @@ -66,15 +59,7 @@ static void BM_BenchmarkFindSkipList(::benchmark::State &state) { static void BM_BenchmarkFindStdMap(::benchmark::State &state) { std::map std_map; - coordinator::Hlc start_timestamp; - storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; - storage::v3::Transaction transaction{start_timestamp, isolation_level}; - auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); - for (auto i{0}; i < state.range(0); ++i) { - std_map.insert({storage::v3::PrimaryKey{storage::v3::PropertyValue{i}}, - storage::v3::LexicographicallyOrderedVertex{storage::v3::Vertex{ - delta, std::vector{storage::v3::PropertyValue{i}}}}}); - } + PrepareData(std_map, state.range(0)); // So we can also have elements that does don't exist std::mt19937 i_generator(std::random_device{}()); std::uniform_int_distribution i_distribution(0, state.range(0) * 2); @@ -92,14 +77,12 @@ static void BM_BenchmarkFindStdMap(::benchmark::State &state) { static void BM_BenchmarkFindStdSet(::benchmark::State &state) { std::set std_set; + PrepareData(std_set, state.range(0)); coordinator::Hlc start_timestamp; storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; storage::v3::Transaction transaction{start_timestamp, isolation_level}; auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); - for (auto i{0}; i < state.range(0); ++i) { - std_set.insert(storage::v3::LexicographicallyOrderedVertex{ - storage::v3::Vertex{delta, std::vector{storage::v3::PropertyValue{i}}}}); - } + // So we can also have elements that does don't exist std::mt19937 i_generator(std::random_device{}()); std::uniform_int_distribution i_distribution(0, state.range(0) * 2); @@ -118,15 +101,7 @@ static void BM_BenchmarkFindStdSet(::benchmark::State &state) { static void BM_BenchmarkFindBppTree(::benchmark::State &state) { tlx::btree_map bpp_tree; - coordinator::Hlc start_timestamp; - storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; - storage::v3::Transaction transaction{start_timestamp, isolation_level}; - auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); - for (auto i{0}; i < state.range(0); ++i) { - bpp_tree.insert({storage::v3::PrimaryKey{storage::v3::PropertyValue{i}}, - storage::v3::LexicographicallyOrderedVertex{storage::v3::Vertex{ - delta, std::vector{storage::v3::PropertyValue{i}}}}}); - } + PrepareData(bpp_tree, state.range(0)); // So we can also have elements that does don't exist std::mt19937 i_generator(std::random_device{}()); std::uniform_int_distribution i_distribution(0, state.range(0) * 2); diff --git a/tests/benchmark/data_structures_insert.cpp b/tests/benchmark/data_structures_insert.cpp index be2d7457f..4d427985b 100644 --- a/tests/benchmark/data_structures_insert.cpp +++ b/tests/benchmark/data_structures_insert.cpp @@ -24,7 +24,6 @@ #include #include "btree_map.hpp" -#include "skip_list_common.hpp" #include "storage/v3/key_store.hpp" #include "storage/v3/lexicographically_ordered_vertex.hpp" #include "storage/v3/mvcc.hpp" From 36a7abb17080de344d9c7fb75e21ec689ee69687 Mon Sep 17 00:00:00 2001 From: jbajic Date: Sun, 27 Nov 2022 22:50:36 +0100 Subject: [PATCH 06/16] Add remove benchmark --- tests/benchmark/CMakeLists.txt | 2 +- tests/benchmark/data_structures_remove.cpp | 138 +++++++++++++++++++++ 2 files changed, 139 insertions(+), 1 deletion(-) create mode 100644 tests/benchmark/data_structures_remove.cpp diff --git a/tests/benchmark/CMakeLists.txt b/tests/benchmark/CMakeLists.txt index 6ec3fa644..61a4cd62a 100644 --- a/tests/benchmark/CMakeLists.txt +++ b/tests/benchmark/CMakeLists.txt @@ -81,4 +81,4 @@ add_benchmark(data_structures_contains.cpp) target_link_libraries(${test_prefix}data_structures_contains mg-utils mg-storage-v3) add_benchmark(data_structures_remove.cpp) -target_link_libraries(${test_prefix}data_structures_contains mg-utils mg-storage-v3) +target_link_libraries(${test_prefix}data_structures_remove mg-utils mg-storage-v3) diff --git a/tests/benchmark/data_structures_remove.cpp b/tests/benchmark/data_structures_remove.cpp new file mode 100644 index 000000000..3fbd54a38 --- /dev/null +++ b/tests/benchmark/data_structures_remove.cpp @@ -0,0 +1,138 @@ +// Copyright 2022 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "btree_map.hpp" +#include "data_structures_common.hpp" +#include "storage/v3/key_store.hpp" +#include "storage/v3/lexicographically_ordered_vertex.hpp" +#include "storage/v3/mvcc.hpp" +#include "storage/v3/property_value.hpp" +#include "storage/v3/transaction.hpp" +#include "storage/v3/vertex.hpp" +#include "utils/skip_list.hpp" + +namespace memgraph::benchmark { + +/////////////////////////////////////////////////////////////////////////////// +// Testing Remove Operation +/////////////////////////////////////////////////////////////////////////////// +static void BM_BenchmarkRemoveSkipList(::benchmark::State &state) { + utils::SkipList skip_list; + PrepareData(skip_list, state.range(0)); + coordinator::Hlc start_timestamp; + storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; + storage::v3::Transaction transaction{start_timestamp, isolation_level}; + auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); + + // So we can also have elements that does don't exist + std::mt19937 i_generator(std::random_device{}()); + std::uniform_int_distribution i_distribution(0, state.range(0) * 2); + int64_t removed_elems{0}; + for (auto _ : state) { + for (auto i{0}; i < state.range(0); ++i) { + int64_t value = i_distribution(i_generator); + auto acc = skip_list.access(); + if (acc.remove(storage::v3::LexicographicallyOrderedVertex{ + storage::v3::Vertex{delta, storage::v3::PrimaryKey{storage::v3::PropertyValue(value)}}})) { + removed_elems++; + } + } + } + state.SetItemsProcessed(removed_elems); +} + +static void BM_BenchmarkRemoveStdMap(::benchmark::State &state) { + std::map std_map; + PrepareData(std_map, state.range(0)); + + // So we can also have elements that does don't exist + std::mt19937 i_generator(std::random_device{}()); + std::uniform_int_distribution i_distribution(0, state.range(0) * 2); + int64_t removed_elems{0}; + for (auto _ : state) { + for (auto i{0}; i < state.range(0); ++i) { + int64_t value = i_distribution(i_generator); + if (std_map.erase(storage::v3::PrimaryKey{storage::v3::PropertyValue{value}}) > 0) { + removed_elems++; + } + } + } + state.SetItemsProcessed(removed_elems); +} + +static void BM_BenchmarkRemoveStdSet(::benchmark::State &state) { + std::set std_set; + PrepareData(std_set, state.range(0)); + coordinator::Hlc start_timestamp; + storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; + storage::v3::Transaction transaction{start_timestamp, isolation_level}; + auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); + + // So we can also have elements that does don't exist + std::mt19937 i_generator(std::random_device{}()); + std::uniform_int_distribution i_distribution(0, state.range(0) * 2); + int64_t removed_elems{0}; + for (auto _ : state) { + for (auto i{0}; i < state.range(0); ++i) { + int64_t value = i_distribution(i_generator); + if (std_set.erase(storage::v3::LexicographicallyOrderedVertex{ + storage::v3::Vertex{delta, {storage::v3::PropertyValue{value}}}}) > 0) { + removed_elems++; + } + } + } + state.SetItemsProcessed(removed_elems); +} + +static void BM_BenchmarkRemoveBppTree(::benchmark::State &state) { + tlx::btree_map bpp_tree; + PrepareData(bpp_tree, state.range(0)); + + // So we can also have elements that does don't exist + std::mt19937 i_generator(std::random_device{}()); + std::uniform_int_distribution i_distribution(0, state.range(0) * 2); + int64_t removed_elems{0}; + for (auto _ : state) { + for (auto i{0}; i < state.range(0); ++i) { + int64_t value = i_distribution(i_generator); + if (bpp_tree.erase(storage::v3::PrimaryKey{storage::v3::PropertyValue{value}}) > 0) { + removed_elems++; + } + } + } + state.SetItemsProcessed(removed_elems); +} + +BENCHMARK(BM_BenchmarkRemoveSkipList)->Arg(1000); + +BENCHMARK(BM_BenchmarkRemoveStdMap)->Arg(1000); + +BENCHMARK(BM_BenchmarkRemoveStdSet)->Arg(1000); + +BENCHMARK(BM_BenchmarkRemoveBppTree)->Arg(1000); + +} // namespace memgraph::benchmark + +BENCHMARK_MAIN(); From 500691318a005c712ae4a3ca89e4b6e743d92bd4 Mon Sep 17 00:00:00 2001 From: jbajic Date: Mon, 28 Nov 2022 14:15:41 +0100 Subject: [PATCH 07/16] Add analyze script --- tests/benchmark/data_structures_contains.cpp | 10 +- tests/benchmark/data_structures_find.cpp | 8 +- tests/benchmark/data_structures_insert.cpp | 8 +- tests/benchmark/data_structures_remove.cpp | 8 +- tools/plot/benchmark_datastructures.py | 172 +++++++++++++++++++ tools/plot/benchmark_datastructures.sh | 16 ++ 6 files changed, 205 insertions(+), 17 deletions(-) create mode 100644 tools/plot/benchmark_datastructures.py create mode 100755 tools/plot/benchmark_datastructures.sh diff --git a/tests/benchmark/data_structures_contains.cpp b/tests/benchmark/data_structures_contains.cpp index 08596c7a7..bf1a74a8d 100644 --- a/tests/benchmark/data_structures_contains.cpp +++ b/tests/benchmark/data_structures_contains.cpp @@ -103,7 +103,7 @@ static void BM_BenchmarkContainsBppTree(::benchmark::State &state) { tlx::btree_map bpp_tree; PrepareData(bpp_tree, state.range(0)); - // So we can also have elements that does don't exist + // So we can also have elements that does don't exists std::mt19937 i_generator(std::random_device{}()); std::uniform_int_distribution i_distribution(0, state.range(0) * 2); int64_t found_elems{0}; @@ -118,13 +118,13 @@ static void BM_BenchmarkContainsBppTree(::benchmark::State &state) { state.SetItemsProcessed(found_elems); } -BENCHMARK(BM_BenchmarkContainsSkipList)->Arg(1000); +BENCHMARK(BM_BenchmarkContainsSkipList)->RangeMultiplier(10)->Range(1000, 1000000)->Unit(::benchmark::kMillisecond); -BENCHMARK(BM_BenchmarkContainsStdMap)->Arg(1000); +BENCHMARK(BM_BenchmarkContainsStdMap)->RangeMultiplier(10)->Range(1000, 1000000)->Unit(::benchmark::kMillisecond); -BENCHMARK(BM_BenchmarkContainsStdSet)->Arg(1000); +BENCHMARK(BM_BenchmarkContainsStdSet)->RangeMultiplier(10)->Range(1000, 1000000)->Unit(::benchmark::kMillisecond); -BENCHMARK(BM_BenchmarkContainsBppTree)->Arg(1000); +BENCHMARK(BM_BenchmarkContainsBppTree)->RangeMultiplier(10)->Range(1000, 1000000)->Unit(::benchmark::kMillisecond); } // namespace memgraph::benchmark diff --git a/tests/benchmark/data_structures_find.cpp b/tests/benchmark/data_structures_find.cpp index 042066d68..d3b3bdd60 100644 --- a/tests/benchmark/data_structures_find.cpp +++ b/tests/benchmark/data_structures_find.cpp @@ -117,13 +117,13 @@ static void BM_BenchmarkFindBppTree(::benchmark::State &state) { state.SetItemsProcessed(found_elems); } -BENCHMARK(BM_BenchmarkFindSkipList)->Arg(1000); +BENCHMARK(BM_BenchmarkFindSkipList)->RangeMultiplier(10)->Range(1000, 1000000)->Unit(::benchmark::kMillisecond); -BENCHMARK(BM_BenchmarkFindStdMap)->Arg(1000); +BENCHMARK(BM_BenchmarkFindStdMap)->RangeMultiplier(10)->Range(1000, 1000000)->Unit(::benchmark::kMillisecond); -BENCHMARK(BM_BenchmarkFindStdSet)->Arg(1000); +BENCHMARK(BM_BenchmarkFindStdSet)->RangeMultiplier(10)->Range(1000, 1000000)->Unit(::benchmark::kMillisecond); -BENCHMARK(BM_BenchmarkFindBppTree)->Arg(1000); +BENCHMARK(BM_BenchmarkFindBppTree)->RangeMultiplier(10)->Range(1000, 1000000)->Unit(::benchmark::kMillisecond); } // namespace memgraph::benchmark diff --git a/tests/benchmark/data_structures_insert.cpp b/tests/benchmark/data_structures_insert.cpp index 4d427985b..8882b444e 100644 --- a/tests/benchmark/data_structures_insert.cpp +++ b/tests/benchmark/data_structures_insert.cpp @@ -99,13 +99,13 @@ static void BM_BenchmarkInsertBppTree(::benchmark::State &state) { } } -BENCHMARK(BM_BenchmarkInsertSkipList)->Arg(1000); +BENCHMARK(BM_BenchmarkInsertSkipList)->RangeMultiplier(10)->Range(1000, 1000000)->Unit(::benchmark::kMillisecond); -BENCHMARK(BM_BenchmarkInsertStdMap)->Arg(1000); +BENCHMARK(BM_BenchmarkInsertStdMap)->RangeMultiplier(10)->Range(1000, 1000000)->Unit(::benchmark::kMillisecond); -BENCHMARK(BM_BenchmarkInsertStdSet)->Arg(1000); +BENCHMARK(BM_BenchmarkInsertStdSet)->RangeMultiplier(10)->Range(1000, 1000000)->Unit(::benchmark::kMillisecond); -BENCHMARK(BM_BenchmarkInsertBppTree)->Arg(1000); +BENCHMARK(BM_BenchmarkInsertBppTree)->RangeMultiplier(10)->Range(1000, 1000000)->Unit(::benchmark::kMillisecond); } // namespace memgraph::benchmark diff --git a/tests/benchmark/data_structures_remove.cpp b/tests/benchmark/data_structures_remove.cpp index 3fbd54a38..5296a9130 100644 --- a/tests/benchmark/data_structures_remove.cpp +++ b/tests/benchmark/data_structures_remove.cpp @@ -125,13 +125,13 @@ static void BM_BenchmarkRemoveBppTree(::benchmark::State &state) { state.SetItemsProcessed(removed_elems); } -BENCHMARK(BM_BenchmarkRemoveSkipList)->Arg(1000); +BENCHMARK(BM_BenchmarkRemoveSkipList)->RangeMultiplier(10)->Range(1000, 1000000)->Unit(::benchmark::kMillisecond); -BENCHMARK(BM_BenchmarkRemoveStdMap)->Arg(1000); +BENCHMARK(BM_BenchmarkRemoveStdMap)->RangeMultiplier(10)->Range(1000, 1000000)->Unit(::benchmark::kMillisecond); -BENCHMARK(BM_BenchmarkRemoveStdSet)->Arg(1000); +BENCHMARK(BM_BenchmarkRemoveStdSet)->RangeMultiplier(10)->Range(1000, 1000000)->Unit(::benchmark::kMillisecond); -BENCHMARK(BM_BenchmarkRemoveBppTree)->Arg(1000); +BENCHMARK(BM_BenchmarkRemoveBppTree)->RangeMultiplier(10)->Range(1000, 1000000)->Unit(::benchmark::kMillisecond); } // namespace memgraph::benchmark diff --git a/tools/plot/benchmark_datastructures.py b/tools/plot/benchmark_datastructures.py new file mode 100644 index 000000000..29fa61be7 --- /dev/null +++ b/tools/plot/benchmark_datastructures.py @@ -0,0 +1,172 @@ +# Copyright 2022 Memgraph Ltd. +# +# Use of this software is governed by the Business Source License +# included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +# License, and you may not use this file except in compliance with the Business Source License. +# +# As of the Change Date specified in that file, in accordance with +# the Business Source License, use of this software will be governed +# by the Apache License, Version 2.0, included in the file +# licenses/APL.txt. + +import argparse +import json +import sys +from dataclasses import dataclass +from enum import Enum +from pathlib import Path +from typing import Any, Dict, List, Optional + +import matplotlib.pyplot as plt + + +class Operation(Enum): + CONTAINS = "contains" + FIND = "find" + INSERT = "insert" + RANDOM = "random" + REMOVE = "remove" + + @classmethod + def to_list(cls) -> List[str]: + return list(map(lambda c: c.value, cls)) + + @staticmethod + def get(s: str) -> Optional["Operation"]: + try: + return Operation[s.upper()] + except ValueError: + return None + + def __str__(self): + return str(self.value) + + +@dataclass(frozen=True) +class BenchmarkRow: + name: str + datastructure: str + operation: Operation + real_time: int + cpu_time: int + iterations: int + time_unit: str + run_arg: Optional[Any] + + +class GoogleBenchmarkResult: + def __init__(self): + self._operation = None + self._datastructures: Dict[str, List[BenchmarkRow]] = dict() + + def add_result(self, row: BenchmarkRow) -> None: + if self._operation is None: + self._operation = row.operation + assert self._operation is row.operation + if row.datastructure not in self._datastructures: + self._datastructures[row.datastructure] = [row] + else: + self._datastructures[row.datastructure].append(row) + + @property + def operation(self) -> Optional[Operation]: + return self._operation + + @property + def datastructures(self) -> Dict[str, List[BenchmarkRow]]: + return self._datastructures + + +def get_operation(s: str) -> Operation: + for op in Operation.to_list(): + if op.lower() in s.lower(): + operation_enum = Operation.get(op) + if operation_enum is not None: + return operation_enum + else: + print("Operation not found!") + sys.exit(1) + print("Operation not found!") + sys.exit(1) + + +def get_row_data(line: Dict[str, Any]) -> BenchmarkRow: + """ + Naming is very important, first must come an Operation name, and then a data + structure to test. + """ + full_name = line["name"].split("BM_Benchmark")[1] + name_with_run_arg = full_name.split("/") + operation = get_operation(name_with_run_arg[0]) + datastructure = name_with_run_arg[0].split(operation.value.capitalize())[1] + + run_arg = None + if len(name_with_run_arg) > 1: + run_arg = name_with_run_arg[1] + + return BenchmarkRow( + name_with_run_arg[0], + datastructure, + operation, + line["real_time"], + line["cpu_time"], + line["iterations"], + line["time_unit"], + run_arg, + ) + + +def get_benchmark_res(args) -> Optional[GoogleBenchmarkResult]: + file_path = Path(args.log_file) + if not file_path.exists(): + print("Error file {file_path} not found!") + return None + with file_path.open("r") as file: + data = json.load(file) + res = GoogleBenchmarkResult() + assert "benchmarks" in data, "There must be a benchmark list inside" + for benchmark in data["benchmarks"]: + res.add_result(get_row_data(benchmark)) + return res + + +def plot_operation(results: GoogleBenchmarkResult, save: bool) -> None: + colors = ["red", "green", "blue", "yellow", "purple", "brown"] + assert results.operation is not None + fig = plt.figure() + for ds, benchmarks in results.datastructures.items(): + if benchmarks: + # Print line chart + x_axis = [elem.real_time for elem in benchmarks] + y_axis = [elem.run_arg for elem in benchmarks] + plt.plot(x_axis, y_axis, marker="", color=colors.pop(0), linewidth="2", label=f"{ds}") + plt.title(f"Benchmark results for operation {results.operation.value}") + plt.xlabel(f"Time [{benchmarks[0].time_unit}]") + plt.legend() + else: + print(f"Nothing to do for {ds}...") + if save: + plt.savefig(f"{results.operation.value}.png") + plt.close(fig) + else: + plt.show() + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser(description="Process benchmark results.") + parser.add_argument("--log_file", type=str) + parser.add_argument("--save", type=bool, default=True) + return parser.parse_args() + + +def main(): + args = parse_args() + res = get_benchmark_res(args) + if res is None: + print("Failed to get results from log file!") + sys.exit(1) + plot_operation(res, args.save) + + +if __name__ == "__main__": + main() diff --git a/tools/plot/benchmark_datastructures.sh b/tools/plot/benchmark_datastructures.sh new file mode 100755 index 000000000..450b38466 --- /dev/null +++ b/tools/plot/benchmark_datastructures.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +set -euox pipefail + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +WORKSPACE_DIR=${SCRIPT_DIR}/../../ + +BENCHMARK_FILES=$(find ${WORKSPACE_DIR}/tests/benchmark -type f -iname data_structures_*) +echo $BENCHMARK_FILES +for bench_file in ${BENCHMARK_FILES}; do + echo "Running $name" + base_name=$(basename $bench_file) + name=${base_name%%.*} + ${WORKSPACE_DIR}/build/tests/benchmark/${name} --benchmark_format=json --benchmark_out=${name}_output.json + python3 ${WORKSPACE_DIR}/tools/plot/benchmark_datastructures.py --log-file=${name}_output.json +done From 5f5d839f0c22a4d611deea0aacce8f171328e454 Mon Sep 17 00:00:00 2001 From: jbajic Date: Mon, 28 Nov 2022 15:11:58 +0100 Subject: [PATCH 08/16] Update analyzer script --- tests/benchmark/data_structures_contains.cpp | 8 +++--- tests/benchmark/data_structures_find.cpp | 8 +++--- tests/benchmark/data_structures_insert.cpp | 8 +++--- tests/benchmark/data_structures_remove.cpp | 8 +++--- tools/plot/benchmark_datastructures.py | 13 +++++++++ tools/plot/benchmark_datastructures.sh | 29 ++++++++++++++------ 6 files changed, 50 insertions(+), 24 deletions(-) diff --git a/tests/benchmark/data_structures_contains.cpp b/tests/benchmark/data_structures_contains.cpp index bf1a74a8d..604ab6ef9 100644 --- a/tests/benchmark/data_structures_contains.cpp +++ b/tests/benchmark/data_structures_contains.cpp @@ -118,13 +118,13 @@ static void BM_BenchmarkContainsBppTree(::benchmark::State &state) { state.SetItemsProcessed(found_elems); } -BENCHMARK(BM_BenchmarkContainsSkipList)->RangeMultiplier(10)->Range(1000, 1000000)->Unit(::benchmark::kMillisecond); +BENCHMARK(BM_BenchmarkContainsSkipList)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond); -BENCHMARK(BM_BenchmarkContainsStdMap)->RangeMultiplier(10)->Range(1000, 1000000)->Unit(::benchmark::kMillisecond); +BENCHMARK(BM_BenchmarkContainsStdMap)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond); -BENCHMARK(BM_BenchmarkContainsStdSet)->RangeMultiplier(10)->Range(1000, 1000000)->Unit(::benchmark::kMillisecond); +BENCHMARK(BM_BenchmarkContainsStdSet)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond); -BENCHMARK(BM_BenchmarkContainsBppTree)->RangeMultiplier(10)->Range(1000, 1000000)->Unit(::benchmark::kMillisecond); +BENCHMARK(BM_BenchmarkContainsBppTree)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond); } // namespace memgraph::benchmark diff --git a/tests/benchmark/data_structures_find.cpp b/tests/benchmark/data_structures_find.cpp index d3b3bdd60..502be91da 100644 --- a/tests/benchmark/data_structures_find.cpp +++ b/tests/benchmark/data_structures_find.cpp @@ -117,13 +117,13 @@ static void BM_BenchmarkFindBppTree(::benchmark::State &state) { state.SetItemsProcessed(found_elems); } -BENCHMARK(BM_BenchmarkFindSkipList)->RangeMultiplier(10)->Range(1000, 1000000)->Unit(::benchmark::kMillisecond); +BENCHMARK(BM_BenchmarkFindSkipList)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond); -BENCHMARK(BM_BenchmarkFindStdMap)->RangeMultiplier(10)->Range(1000, 1000000)->Unit(::benchmark::kMillisecond); +BENCHMARK(BM_BenchmarkFindStdMap)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond); -BENCHMARK(BM_BenchmarkFindStdSet)->RangeMultiplier(10)->Range(1000, 1000000)->Unit(::benchmark::kMillisecond); +BENCHMARK(BM_BenchmarkFindStdSet)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond); -BENCHMARK(BM_BenchmarkFindBppTree)->RangeMultiplier(10)->Range(1000, 1000000)->Unit(::benchmark::kMillisecond); +BENCHMARK(BM_BenchmarkFindBppTree)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond); } // namespace memgraph::benchmark diff --git a/tests/benchmark/data_structures_insert.cpp b/tests/benchmark/data_structures_insert.cpp index 8882b444e..024c23202 100644 --- a/tests/benchmark/data_structures_insert.cpp +++ b/tests/benchmark/data_structures_insert.cpp @@ -99,13 +99,13 @@ static void BM_BenchmarkInsertBppTree(::benchmark::State &state) { } } -BENCHMARK(BM_BenchmarkInsertSkipList)->RangeMultiplier(10)->Range(1000, 1000000)->Unit(::benchmark::kMillisecond); +BENCHMARK(BM_BenchmarkInsertSkipList)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond); -BENCHMARK(BM_BenchmarkInsertStdMap)->RangeMultiplier(10)->Range(1000, 1000000)->Unit(::benchmark::kMillisecond); +BENCHMARK(BM_BenchmarkInsertStdMap)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond); -BENCHMARK(BM_BenchmarkInsertStdSet)->RangeMultiplier(10)->Range(1000, 1000000)->Unit(::benchmark::kMillisecond); +BENCHMARK(BM_BenchmarkInsertStdSet)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond); -BENCHMARK(BM_BenchmarkInsertBppTree)->RangeMultiplier(10)->Range(1000, 1000000)->Unit(::benchmark::kMillisecond); +BENCHMARK(BM_BenchmarkInsertBppTree)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond); } // namespace memgraph::benchmark diff --git a/tests/benchmark/data_structures_remove.cpp b/tests/benchmark/data_structures_remove.cpp index 5296a9130..aa02820fc 100644 --- a/tests/benchmark/data_structures_remove.cpp +++ b/tests/benchmark/data_structures_remove.cpp @@ -125,13 +125,13 @@ static void BM_BenchmarkRemoveBppTree(::benchmark::State &state) { state.SetItemsProcessed(removed_elems); } -BENCHMARK(BM_BenchmarkRemoveSkipList)->RangeMultiplier(10)->Range(1000, 1000000)->Unit(::benchmark::kMillisecond); +BENCHMARK(BM_BenchmarkRemoveSkipList)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond); -BENCHMARK(BM_BenchmarkRemoveStdMap)->RangeMultiplier(10)->Range(1000, 1000000)->Unit(::benchmark::kMillisecond); +BENCHMARK(BM_BenchmarkRemoveStdMap)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond); -BENCHMARK(BM_BenchmarkRemoveStdSet)->RangeMultiplier(10)->Range(1000, 1000000)->Unit(::benchmark::kMillisecond); +BENCHMARK(BM_BenchmarkRemoveStdSet)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond); -BENCHMARK(BM_BenchmarkRemoveBppTree)->RangeMultiplier(10)->Range(1000, 1000000)->Unit(::benchmark::kMillisecond); +BENCHMARK(BM_BenchmarkRemoveBppTree)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond); } // namespace memgraph::benchmark diff --git a/tools/plot/benchmark_datastructures.py b/tools/plot/benchmark_datastructures.py index 29fa61be7..f57516d4d 100644 --- a/tools/plot/benchmark_datastructures.py +++ b/tools/plot/benchmark_datastructures.py @@ -9,6 +9,17 @@ # by the Apache License, Version 2.0, included in the file # licenses/APL.txt. +#################################### +# Benchmark datastructures analyzer +#################################### +# This scripts uses the output from dataset benchmark tests to plot charts +# comparing the results of different datastructures on the same operation. +# +# Note: Naming the tests is very important in order for this script to recognize +# which operation is being performed and on which DS, so it should come in this +# form: BM_Benchmark/ +# where run_argument will be added automatically by google benchmark framework + import argparse import json import sys @@ -142,7 +153,9 @@ def plot_operation(results: GoogleBenchmarkResult, save: bool) -> None: plt.plot(x_axis, y_axis, marker="", color=colors.pop(0), linewidth="2", label=f"{ds}") plt.title(f"Benchmark results for operation {results.operation.value}") plt.xlabel(f"Time [{benchmarks[0].time_unit}]") + plt.grid(True) plt.legend() + plt.draw() else: print(f"Nothing to do for {ds}...") if save: diff --git a/tools/plot/benchmark_datastructures.sh b/tools/plot/benchmark_datastructures.sh index 450b38466..3001171c0 100755 --- a/tools/plot/benchmark_datastructures.sh +++ b/tools/plot/benchmark_datastructures.sh @@ -4,13 +4,26 @@ set -euox pipefail SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" WORKSPACE_DIR=${SCRIPT_DIR}/../../ +CPUS=$(grep -c processor < /proc/cpuinfo) -BENCHMARK_FILES=$(find ${WORKSPACE_DIR}/tests/benchmark -type f -iname data_structures_*) -echo $BENCHMARK_FILES -for bench_file in ${BENCHMARK_FILES}; do - echo "Running $name" - base_name=$(basename $bench_file) - name=${base_name%%.*} - ${WORKSPACE_DIR}/build/tests/benchmark/${name} --benchmark_format=json --benchmark_out=${name}_output.json - python3 ${WORKSPACE_DIR}/tools/plot/benchmark_datastructures.py --log-file=${name}_output.json +# Get all benchmark files +BENCHMARK_FILES=$(find ${WORKSPACE_DIR}/tests/benchmark -type f -iname "data_structures_*") +echo $(ls ${WORKSPACE_DIR}/tests/benchmark) +for BENCH_FILE in ${BENCHMARK_FILES}; do + BASE_NAME=$(basename $BENCH_FILE) + NAME=${BASE_NAME%%.*} + echo "Running $NAME" + TEST_FILE=${WORKSPACE_DIR}/build/tests/benchmark/${NAME} + if [[ -f "${TEST_FILE}" ]]; then + pushd ${WORKSPACE_DIR}/build + make -j${CPUS} memgraph__benchmark__${NAME} + popd + JSON_OUTPUT=${NAME}_output.json + # Run benchmakr test + ${WORKSPACE_DIR}/build/tests/benchmark/${NAME} --benchmark_format=json --benchmark_out=${JSON_OUTPUT} + # Run analyze script for benchmark test + python3 ${WORKSPACE_DIR}/tools/plot/benchmark_datastructures.py --log_file=${JSON_OUTPUT} + else + echo "File ${TEST_FILE} does not exist!" + fi done From 884831ece5849a1ba46fa0132e581f0f7f5f3693 Mon Sep 17 00:00:00 2001 From: jbajic Date: Tue, 29 Nov 2022 11:56:01 +0100 Subject: [PATCH 09/16] Use pk --- tests/benchmark/data_structures_common.hpp | 6 +++--- tests/benchmark/data_structures_contains.cpp | 7 +++---- tests/benchmark/data_structures_find.cpp | 7 +++---- tests/benchmark/data_structures_insert.cpp | 9 ++++----- tests/benchmark/data_structures_remove.cpp | 10 ++++------ 5 files changed, 17 insertions(+), 22 deletions(-) diff --git a/tests/benchmark/data_structures_common.hpp b/tests/benchmark/data_structures_common.hpp index fe6129a22..fb9053c67 100644 --- a/tests/benchmark/data_structures_common.hpp +++ b/tests/benchmark/data_structures_common.hpp @@ -17,6 +17,7 @@ #include "btree_map.hpp" #include "coordinator/hybrid_logical_clock.hpp" +#include "storage/v3/key_store.hpp" #include "storage/v3/lexicographically_ordered_vertex.hpp" #include "storage/v3/mvcc.hpp" #include "storage/v3/transaction.hpp" @@ -32,7 +33,7 @@ inline void PrepareData(utils::SkipList &skip_list, const int64_t num_element auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); for (auto i{0}; i < num_elements; ++i) { auto acc = skip_list.access(); - acc.insert({storage::v3::Vertex(delta, std::vector{storage::v3::PropertyValue{i}})}); + acc.insert({storage::v3::PrimaryKey{storage::v3::PropertyValue{i}}}); } } @@ -56,8 +57,7 @@ inline void PrepareData(std::set &std_set, const int64_t num_elements) { storage::v3::Transaction transaction{start_timestamp, isolation_level}; auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); for (auto i{0}; i < num_elements; ++i) { - std_set.insert(storage::v3::LexicographicallyOrderedVertex{ - storage::v3::Vertex{delta, std::vector{storage::v3::PropertyValue{i}}}}); + std_set.insert(std::vector{storage::v3::PropertyValue{i}}); } } diff --git a/tests/benchmark/data_structures_contains.cpp b/tests/benchmark/data_structures_contains.cpp index 604ab6ef9..2950aeed3 100644 --- a/tests/benchmark/data_structures_contains.cpp +++ b/tests/benchmark/data_structures_contains.cpp @@ -39,7 +39,7 @@ namespace memgraph::benchmark { // Testing Contains Operation /////////////////////////////////////////////////////////////////////////////// static void BM_BenchmarkContainsSkipList(::benchmark::State &state) { - utils::SkipList skip_list; + utils::SkipList skip_list; PrepareData(skip_list, state.range(0)); // So we can also have elements that does don't exist std::mt19937 i_generator(std::random_device{}()); @@ -77,7 +77,7 @@ static void BM_BenchmarkContainsStdMap(::benchmark::State &state) { } static void BM_BenchmarkContainsStdSet(::benchmark::State &state) { - std::set std_set; + std::set std_set; PrepareData(std_set, state.range(0)); coordinator::Hlc start_timestamp; storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; @@ -90,8 +90,7 @@ static void BM_BenchmarkContainsStdSet(::benchmark::State &state) { for (auto _ : state) { for (auto i{0}; i < state.range(0); ++i) { int64_t value = i_distribution(i_generator); - if (std_set.contains(storage::v3::LexicographicallyOrderedVertex{ - storage::v3::Vertex{delta, storage::v3::PrimaryKey{storage::v3::PropertyValue{value}}}})) { + if (std_set.contains(storage::v3::PrimaryKey{storage::v3::PropertyValue{value}})) { found_elems++; } } diff --git a/tests/benchmark/data_structures_find.cpp b/tests/benchmark/data_structures_find.cpp index 502be91da..f1f1955c4 100644 --- a/tests/benchmark/data_structures_find.cpp +++ b/tests/benchmark/data_structures_find.cpp @@ -39,7 +39,7 @@ namespace memgraph::benchmark { // Testing Find Operation /////////////////////////////////////////////////////////////////////////////// static void BM_BenchmarkFindSkipList(::benchmark::State &state) { - utils::SkipList skip_list; + utils::SkipList skip_list; PrepareData(skip_list, state.range(0)); // So we can also have elements that does don't exist std::mt19937 i_generator(std::random_device{}()); @@ -76,7 +76,7 @@ static void BM_BenchmarkFindStdMap(::benchmark::State &state) { } static void BM_BenchmarkFindStdSet(::benchmark::State &state) { - std::set std_set; + std::set std_set; PrepareData(std_set, state.range(0)); coordinator::Hlc start_timestamp; storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; @@ -90,8 +90,7 @@ static void BM_BenchmarkFindStdSet(::benchmark::State &state) { for (auto _ : state) { for (auto i{0}; i < state.range(0); ++i) { int64_t value = i_distribution(i_generator); - if (std_set.find(storage::v3::LexicographicallyOrderedVertex{storage::v3::Vertex{ - delta, storage::v3::PrimaryKey{storage::v3::PropertyValue{value}}}}) != std_set.end()) { + if (std_set.find(storage::v3::PrimaryKey{storage::v3::PropertyValue{value}}) != std_set.end()) { found_elems++; } } diff --git a/tests/benchmark/data_structures_insert.cpp b/tests/benchmark/data_structures_insert.cpp index 024c23202..1e5361d62 100644 --- a/tests/benchmark/data_structures_insert.cpp +++ b/tests/benchmark/data_structures_insert.cpp @@ -38,7 +38,7 @@ namespace memgraph::benchmark { // Testing Insert Operation /////////////////////////////////////////////////////////////////////////////// static void BM_BenchmarkInsertSkipList(::benchmark::State &state) { - utils::SkipList skip_list; + utils::SkipList skip_list; coordinator::Hlc start_timestamp; storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; storage::v3::Transaction transaction{start_timestamp, isolation_level}; @@ -47,7 +47,7 @@ static void BM_BenchmarkInsertSkipList(::benchmark::State &state) { for (auto _ : state) { for (auto i{0}; i < state.range(0); ++i) { auto acc = skip_list.access(); - acc.insert({storage::v3::Vertex(delta, std::vector{storage::v3::PropertyValue{i}})}); + acc.insert({storage::v3::PrimaryKey{storage::v3::PropertyValue{i}}}); } } } @@ -69,7 +69,7 @@ static void BM_BenchmarkInsertStdMap(::benchmark::State &state) { } static void BM_BenchmarkInsertStdSet(::benchmark::State &state) { - std::set std_set; + std::set std_set; coordinator::Hlc start_timestamp; storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; storage::v3::Transaction transaction{start_timestamp, isolation_level}; @@ -77,8 +77,7 @@ static void BM_BenchmarkInsertStdSet(::benchmark::State &state) { for (auto _ : state) { for (auto i{0}; i < state.range(0); ++i) { - std_set.insert(storage::v3::LexicographicallyOrderedVertex{ - storage::v3::Vertex{delta, std::vector{storage::v3::PropertyValue{i}}}}); + std_set.insert(storage::v3::PrimaryKey{std::vector{storage::v3::PropertyValue{i}}}); } } } diff --git a/tests/benchmark/data_structures_remove.cpp b/tests/benchmark/data_structures_remove.cpp index aa02820fc..17dbd1dec 100644 --- a/tests/benchmark/data_structures_remove.cpp +++ b/tests/benchmark/data_structures_remove.cpp @@ -39,7 +39,7 @@ namespace memgraph::benchmark { // Testing Remove Operation /////////////////////////////////////////////////////////////////////////////// static void BM_BenchmarkRemoveSkipList(::benchmark::State &state) { - utils::SkipList skip_list; + utils::SkipList skip_list; PrepareData(skip_list, state.range(0)); coordinator::Hlc start_timestamp; storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; @@ -54,8 +54,7 @@ static void BM_BenchmarkRemoveSkipList(::benchmark::State &state) { for (auto i{0}; i < state.range(0); ++i) { int64_t value = i_distribution(i_generator); auto acc = skip_list.access(); - if (acc.remove(storage::v3::LexicographicallyOrderedVertex{ - storage::v3::Vertex{delta, storage::v3::PrimaryKey{storage::v3::PropertyValue(value)}}})) { + if (acc.remove(storage::v3::PrimaryKey{storage::v3::PropertyValue(value)})) { removed_elems++; } } @@ -83,7 +82,7 @@ static void BM_BenchmarkRemoveStdMap(::benchmark::State &state) { } static void BM_BenchmarkRemoveStdSet(::benchmark::State &state) { - std::set std_set; + std::set std_set; PrepareData(std_set, state.range(0)); coordinator::Hlc start_timestamp; storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; @@ -97,8 +96,7 @@ static void BM_BenchmarkRemoveStdSet(::benchmark::State &state) { for (auto _ : state) { for (auto i{0}; i < state.range(0); ++i) { int64_t value = i_distribution(i_generator); - if (std_set.erase(storage::v3::LexicographicallyOrderedVertex{ - storage::v3::Vertex{delta, {storage::v3::PropertyValue{value}}}}) > 0) { + if (std_set.erase(storage::v3::PrimaryKey{storage::v3::PropertyValue{value}}) > 0) { removed_elems++; } } From ce5f1c2f1738f6cf978e04da90b853e0d29edbfe Mon Sep 17 00:00:00 2001 From: jbajic Date: Tue, 29 Nov 2022 13:08:50 +0100 Subject: [PATCH 10/16] Add memory runner for insert --- tools/plot/benchmark_datastructures.sh | 61 ++++++++++++++++++-------- 1 file changed, 42 insertions(+), 19 deletions(-) diff --git a/tools/plot/benchmark_datastructures.sh b/tools/plot/benchmark_datastructures.sh index 3001171c0..2c9885ad4 100755 --- a/tools/plot/benchmark_datastructures.sh +++ b/tools/plot/benchmark_datastructures.sh @@ -8,22 +8,45 @@ CPUS=$(grep -c processor < /proc/cpuinfo) # Get all benchmark files BENCHMARK_FILES=$(find ${WORKSPACE_DIR}/tests/benchmark -type f -iname "data_structures_*") -echo $(ls ${WORKSPACE_DIR}/tests/benchmark) -for BENCH_FILE in ${BENCHMARK_FILES}; do - BASE_NAME=$(basename $BENCH_FILE) - NAME=${BASE_NAME%%.*} - echo "Running $NAME" - TEST_FILE=${WORKSPACE_DIR}/build/tests/benchmark/${NAME} - if [[ -f "${TEST_FILE}" ]]; then - pushd ${WORKSPACE_DIR}/build - make -j${CPUS} memgraph__benchmark__${NAME} - popd - JSON_OUTPUT=${NAME}_output.json - # Run benchmakr test - ${WORKSPACE_DIR}/build/tests/benchmark/${NAME} --benchmark_format=json --benchmark_out=${JSON_OUTPUT} - # Run analyze script for benchmark test - python3 ${WORKSPACE_DIR}/tools/plot/benchmark_datastructures.py --log_file=${JSON_OUTPUT} - else - echo "File ${TEST_FILE} does not exist!" - fi -done + +function test_all() { + for BENCH_FILE in ${BENCHMARK_FILES}; do + local BASE_NAME=$(basename $BENCH_FILE) + local NAME=${BASE_NAME%%.*} + echo "Running $NAME" + local TEST_FILE=${WORKSPACE_DIR}/build/tests/benchmark/${NAME} + if [[ -f "${TEST_FILE}" ]]; then + pushd ${WORKSPACE_DIR}/build + make -j${CPUS} memgraph__benchmark__${NAME} + popd + local JSON_OUTPUT=${NAME}_output.json + # Run benchmakr test + ${WORKSPACE_DIR}/build/tests/benchmark/${NAME} --benchmark_format=json --benchmark_out=${JSON_OUTPUT} + # Run analyze script for benchmark test + python3 ${WORKSPACE_DIR}/tools/plot/benchmark_datastructures.py --log_file=${JSON_OUTPUT} + else + echo "File ${TEST_FILE} does not exist!" + fi + done +} + +function test_memory() { + ## We are testing only insert + local DATA_STRUCTURES=(SkipList StdMap StdSet BppTree) + for DATA_STRUCTURE in ${DATA_STRUCTURES[@]}; do + valgrind --tool=massif --massif-out-file=${DATA_STRUCTURE}.massif.out ${WORKSPACE_DIR}/build/tests/benchmark/data_structures_insert --benchmark_filter=BM_BenchmarkInsert${DATA_STRUCTURE}/10000 --benchmark_format=json --benchmark_out=${DATA_STRUCTURE}.json + done +} + +ARG_1=${1:-"all"} +case ${ARG_1} in + all) + test_all + ;; + memory) + test_memory + ;; + *) + echo "Select either `all` or `memory` benchmark!" + ;; +esac From 632db4175aa6c8556aa7b969a30f72522df40a2d Mon Sep 17 00:00:00 2001 From: jbajic Date: Tue, 29 Nov 2022 14:01:39 +0100 Subject: [PATCH 11/16] Revert storage changes --- src/storage/v3/key_store.hpp | 5 +- src/storage/v3/property_store.cpp | 10 -- src/storage/v3/property_store.hpp | 4 +- src/storage/v3/vertex.hpp | 2 - tests/benchmark/data_structures_random.cpp | 169 --------------------- tools/plot/benchmark_datastructures.sh | 2 +- 6 files changed, 5 insertions(+), 187 deletions(-) delete mode 100644 tests/benchmark/data_structures_random.cpp diff --git a/src/storage/v3/key_store.hpp b/src/storage/v3/key_store.hpp index 1762273fb..4bc3c25e3 100644 --- a/src/storage/v3/key_store.hpp +++ b/src/storage/v3/key_store.hpp @@ -26,12 +26,11 @@ using PrimaryKey = std::vector; class KeyStore { public: - KeyStore() = default; explicit KeyStore(const PrimaryKey &key_values); - KeyStore(const KeyStore &) = default; + KeyStore(const KeyStore &) = delete; KeyStore(KeyStore &&other) noexcept = default; - KeyStore &operator=(const KeyStore &) = default; + KeyStore &operator=(const KeyStore &) = delete; KeyStore &operator=(KeyStore &&other) noexcept = default; ~KeyStore() = default; diff --git a/src/storage/v3/property_store.cpp b/src/storage/v3/property_store.cpp index bf8460003..fc7bd6984 100644 --- a/src/storage/v3/property_store.cpp +++ b/src/storage/v3/property_store.cpp @@ -928,16 +928,6 @@ void SetSizeData(uint8_t *buffer, uint64_t size, uint8_t *data) { PropertyStore::PropertyStore() { memset(buffer_, 0, sizeof(buffer_)); } -PropertyStore::PropertyStore(const PropertyStore &other) { memcpy(buffer_, other.buffer_, sizeof(buffer_)); } - -PropertyStore &PropertyStore::operator=(const PropertyStore &other) { - if (this == &other) { - return *this; - } - memcpy(buffer_, other.buffer_, sizeof(buffer_)); - return *this; -} - PropertyStore::PropertyStore(PropertyStore &&other) noexcept { memcpy(buffer_, other.buffer_, sizeof(buffer_)); memset(other.buffer_, 0, sizeof(other.buffer_)); diff --git a/src/storage/v3/property_store.hpp b/src/storage/v3/property_store.hpp index 00dc4d40f..477795203 100644 --- a/src/storage/v3/property_store.hpp +++ b/src/storage/v3/property_store.hpp @@ -25,9 +25,9 @@ class PropertyStore { public: PropertyStore(); - PropertyStore(const PropertyStore &); + PropertyStore(const PropertyStore &) = delete; PropertyStore(PropertyStore &&other) noexcept; - PropertyStore &operator=(const PropertyStore &); + PropertyStore &operator=(const PropertyStore &) = delete; PropertyStore &operator=(PropertyStore &&other) noexcept; ~PropertyStore(); diff --git a/src/storage/v3/vertex.hpp b/src/storage/v3/vertex.hpp index cbfac4ee8..2ed4c6e4c 100644 --- a/src/storage/v3/vertex.hpp +++ b/src/storage/v3/vertex.hpp @@ -31,8 +31,6 @@ namespace memgraph::storage::v3 { struct Vertex { using EdgeLink = std::tuple; - Vertex() = default; - Vertex(Delta *delta, const std::vector &primary_properties) : keys{primary_properties}, delta{delta} { MG_ASSERT(delta == nullptr || delta->action == Delta::Action::DELETE_OBJECT, "Vertex must be created with an initial DELETE_OBJECT delta!"); diff --git a/tests/benchmark/data_structures_random.cpp b/tests/benchmark/data_structures_random.cpp deleted file mode 100644 index f130d6faf..000000000 --- a/tests/benchmark/data_structures_random.cpp +++ /dev/null @@ -1,169 +0,0 @@ -// Copyright 2022 Memgraph Ltd. -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source -// License, and you may not use this file except in compliance with the Business Source License. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "btree_map.hpp" -#include "skip_list_common.hpp" -#include "utils/skip_list.hpp" - -DEFINE_int32(max_element, 20000, "Maximum element in the intial list"); -DEFINE_int32(max_range, 2000000, "Maximum range used for the test"); -DEFINE_string(ds, "skip_list", "Which DS to test"); - -template -concept BasicIndexStructure = requires(ContainerType a, const ContainerType b) { - { a.contains() } -> std::same_as; - { a.find() } -> std::same_as; - { a.insert() } -> std::same_as>; - { a.remove() } -> std::same_as; -}; - -template -class BppStructure final { - using value_type = typename tlx::btree_map::value_type; - using iterator = typename tlx::btree_map::iterator; - - public: - bool contains(const T key) const { return data.exists(key); } - - auto find(const T key) const { return data.find(key); } - - std::pair insert(T val) { return data.insert({val, val}); } - - bool remove(const T key) { return data.erase(key); } - - size_t size() const { return data.size(); }; - - iterator end() { return data.end(); } - - private: - tlx::btree_map data; -}; - -template -class MapStructure final { - using value_type = typename std::map::value_type; - using iterator = typename std::map::iterator; - - public: - bool contains(const T key) const { return data.contains(key); } - - auto find(const T key) const { return data.find(key); } - - std::pair insert(T val) { return data.insert({val, val}); } - - bool remove(const T key) { return data.erase(key); } - - size_t size() const { return data.size(); }; - - iterator end() { return data.end(); } - - private: - std::map data; -}; - -template -void RunDSTest(DataStructure &ds) { - RunTest([&ds](const std::atomic &run, auto &stats) { - std::mt19937 generator(std::random_device{}()); - std::uniform_int_distribution distribution(0, 3); - std::mt19937 i_generator(std::random_device{}()); - std::uniform_int_distribution i_distribution(0, FLAGS_max_range); - - while (run.load(std::memory_order_relaxed)) { - auto value = distribution(generator); - - auto item = i_distribution(i_generator); - switch (value) { - case 0: - stats.succ[OP_INSERT] += static_cast(ds.insert(item).second); - break; - case 1: - stats.succ[OP_CONTAINS] += static_cast(ds.contains(item)); - break; - case 2: - stats.succ[OP_REMOVE] += static_cast(ds.remove(item)); - break; - case 3: - stats.succ[OP_FIND] += static_cast(ds.find(item) != ds.end()); - break; - default: - std::terminate(); - } - ++stats.total; - } - }); -} - -template -void GenerateData(Accessor &acc) { - for (uint64_t i = 0; i <= FLAGS_max_element; ++i) { - MG_ASSERT(acc.insert(i).second); - } -} - -template -void AsserData(Accessor &acc) { - if constexpr (std::is_same_v>) { - uint64_t val = 0; - for (auto item : acc) { - MG_ASSERT(item == val); - ++val; - } - MG_ASSERT(val == FLAGS_max_element + 1); - } else { - MG_ASSERT(acc.size() == FLAGS_max_element + 1); - } -} - -int main(int argc, char **argv) { - gflags::ParseCommandLineFlags(&argc, &argv, true); - - if (FLAGS_ds == "skip_list") { - std::cout << "#### Testing skip list" << std::endl; - memgraph::utils::SkipList list; - auto acc = list.access(); - - GenerateData(acc); - AsserData(acc); - - RunDSTest(acc); - } else if (FLAGS_ds == "map") { - std::cout << "#### Testing map" << std::endl; - MapStructure map; - - GenerateData(map); - AsserData(map); - - RunDSTest(map); - } else if (FLAGS_ds == "bpp") { - std::cout << "#### Testing B+ tree" << std::endl; - BppStructure bpp; - - GenerateData(bpp); - AsserData(bpp); - - RunDSTest(bpp); - } else { - throw std::runtime_error("Select an existing data structure!"); - } - - return 0; -} diff --git a/tools/plot/benchmark_datastructures.sh b/tools/plot/benchmark_datastructures.sh index 2c9885ad4..2ab15be31 100755 --- a/tools/plot/benchmark_datastructures.sh +++ b/tools/plot/benchmark_datastructures.sh @@ -10,7 +10,7 @@ CPUS=$(grep -c processor < /proc/cpuinfo) BENCHMARK_FILES=$(find ${WORKSPACE_DIR}/tests/benchmark -type f -iname "data_structures_*") function test_all() { - for BENCH_FILE in ${BENCHMARK_FILES}; do + for BENCH_FILE in ${BENCHMARK_FILES[@]}; do local BASE_NAME=$(basename $BENCH_FILE) local NAME=${BASE_NAME%%.*} echo "Running $NAME" From 1f98d33fa6d42a4a320e1518e57ad9440155474d Mon Sep 17 00:00:00 2001 From: jbajic Date: Tue, 29 Nov 2022 14:40:44 +0100 Subject: [PATCH 12/16] Remove b++ tree --- tests/benchmark/CMakeLists.txt | 3 - tests/benchmark/btree.hpp | 3134 ------------------ tests/benchmark/btree_map.hpp | 475 --- tests/benchmark/core.hpp | 267 -- tests/benchmark/data_structures_common.hpp | 15 - tests/benchmark/data_structures_contains.cpp | 27 +- tests/benchmark/data_structures_find.cpp | 25 - tests/benchmark/data_structures_insert.cpp | 19 - tests/benchmark/data_structures_remove.cpp | 30 - 9 files changed, 1 insertion(+), 3994 deletions(-) delete mode 100644 tests/benchmark/btree.hpp delete mode 100644 tests/benchmark/btree_map.hpp delete mode 100644 tests/benchmark/core.hpp diff --git a/tests/benchmark/CMakeLists.txt b/tests/benchmark/CMakeLists.txt index 61a4cd62a..c7cededd8 100644 --- a/tests/benchmark/CMakeLists.txt +++ b/tests/benchmark/CMakeLists.txt @@ -68,9 +68,6 @@ target_link_libraries(${test_prefix}storage_v2_property_store mg-storage-v2) add_benchmark(future.cpp) target_link_libraries(${test_prefix}future mg-io) -add_benchmark(data_structures_random.cpp) -target_link_libraries(${test_prefix}data_structures_random mg-utils) - add_benchmark(data_structures_insert.cpp) target_link_libraries(${test_prefix}data_structures_insert mg-utils mg-storage-v3) diff --git a/tests/benchmark/btree.hpp b/tests/benchmark/btree.hpp deleted file mode 100644 index 97b912fb0..000000000 --- a/tests/benchmark/btree.hpp +++ /dev/null @@ -1,3134 +0,0 @@ -/******************************************************************************* - * tlx/container/btree.hpp - * - * Part of tlx - http://panthema.net/tlx - * - * Copyright (C) 2008-2017 Timo Bingmann - * - * All rights reserved. Published under the Boost Software License, Version 1.0 - ******************************************************************************/ - -#ifndef TLX_CONTAINER_BTREE_HEADER -#define TLX_CONTAINER_BTREE_HEADER - -#include "core.hpp" - -// *** Required Headers from the STL - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace tlx { - -//! \addtogroup tlx_container -//! \{ -//! \defgroup tlx_container_btree B+ Trees -//! B+ tree variants -//! \{ - -// *** Debugging Macros - -#ifdef TLX_BTREE_DEBUG - -#include - -//! Print out debug information to std::cout if TLX_BTREE_DEBUG is defined. -#define TLX_BTREE_PRINT(x) \ - do { \ - if (debug) (std::cout << x << std::endl); \ - } while (0) - -//! Assertion only if TLX_BTREE_DEBUG is defined. This is not used in verify(). -#define TLX_BTREE_ASSERT(x) \ - do { \ - assert(x); \ - } while (0) - -#else - -//! Print out debug information to std::cout if TLX_BTREE_DEBUG is defined. -#define TLX_BTREE_PRINT(x) \ - do { \ - } while (0) - -//! Assertion only if TLX_BTREE_DEBUG is defined. This is not used in verify(). -#define TLX_BTREE_ASSERT(x) \ - do { \ - } while (0) - -#endif - -//! The maximum of a and b. Used in some compile-time formulas. -#define TLX_BTREE_MAX(a, b) ((a) < (b) ? (b) : (a)) - -#ifndef TLX_BTREE_FRIENDS -//! The macro TLX_BTREE_FRIENDS can be used by outside class to access the B+ -//! tree internals. This was added for wxBTreeDemo to be able to draw the -//! tree. -#define TLX_BTREE_FRIENDS friend class btree_friend -#endif - -/*! - * Generates default traits for a B+ tree used as a set or map. It estimates - * leaf and inner node sizes by assuming a cache line multiple of 256 bytes. - */ -template -struct btree_default_traits { - //! If true, the tree will self verify its invariants after each insert() or - //! erase(). The header must have been compiled with TLX_BTREE_DEBUG - //! defined. - static const bool self_verify = false; - - //! If true, the tree will print out debug information and a tree dump - //! during insert() or erase() operation. The header must have been - //! compiled with TLX_BTREE_DEBUG defined and key_type must be std::ostream - //! printable. - static const bool debug = false; - - //! Number of slots in each leaf of the tree. Estimated so that each node - //! has a size of about 256 bytes. - static const int leaf_slots = TLX_BTREE_MAX(8, 256 / (sizeof(Value))); - - //! Number of slots in each inner node of the tree. Estimated so that each - //! node has a size of about 256 bytes. - static const int inner_slots = TLX_BTREE_MAX(8, 256 / (sizeof(Key) + sizeof(void *))); - - //! As of stx-btree-0.9, the code does linear search in find_lower() and - //! find_upper() instead of binary_search, unless the node size is larger - //! than this threshold. See notes at - //! http://panthema.net/2013/0504-STX-B+Tree-Binary-vs-Linear-Search - static const size_t binsearch_threshold = 256; -}; - -/*! - * Basic class implementing a B+ tree data structure in memory. - * - * The base implementation of an in-memory B+ tree. It is based on the - * implementation in Cormen's Introduction into Algorithms, Jan Jannink's paper - * and other algorithm resources. Almost all STL-required function calls are - * implemented. The asymptotic time requirements of the STL are not always - * fulfilled in theory, however, in practice this B+ tree performs better than a - * red-black tree and almost always uses less memory. The insertion function - * splits the nodes on the recursion unroll. Erase is largely based on Jannink's - * ideas. - * - * This class is specialized into btree_set, btree_multiset, btree_map and - * btree_multimap using default template parameters and facade functions. - */ -template , - typename Traits = btree_default_traits, bool Duplicates = false, - typename Allocator = std::allocator> -class BTree { - public: - //! \name Template Parameter Types - //! \{ - - //! First template parameter: The key type of the B+ tree. This is stored in - //! inner nodes. - typedef Key key_type; - - //! Second template parameter: Composition pair of key and data types, or - //! just the key for set containers. This data type is stored in the leaves. - typedef Value value_type; - - //! Third template: key extractor class to pull key_type from value_type. - typedef KeyOfValue key_of_value; - - //! Fourth template parameter: key_type comparison function object - typedef Compare key_compare; - - //! Fifth template parameter: Traits object used to define more parameters - //! of the B+ tree - typedef Traits traits; - - //! Sixth template parameter: Allow duplicate keys in the B+ tree. Used to - //! implement multiset and multimap. - static const bool allow_duplicates = Duplicates; - - //! Seventh template parameter: STL allocator for tree nodes - typedef Allocator allocator_type; - - //! \} - - // The macro TLX_BTREE_FRIENDS can be used by outside class to access the B+ - // tree internals. This was added for wxBTreeDemo to be able to draw the - // tree. - TLX_BTREE_FRIENDS; - - public: - //! \name Constructed Types - //! \{ - - //! Typedef of our own type - typedef BTree Self; - - //! Size type used to count keys - typedef size_t size_type; - - //! \} - - public: - //! \name Static Constant Options and Values of the B+ Tree - //! \{ - - //! Base B+ tree parameter: The number of key/data slots in each leaf - static const unsigned short leaf_slotmax = traits::leaf_slots; - - //! Base B+ tree parameter: The number of key slots in each inner node, - //! this can differ from slots in each leaf. - static const unsigned short inner_slotmax = traits::inner_slots; - - //! Computed B+ tree parameter: The minimum number of key/data slots used - //! in a leaf. If fewer slots are used, the leaf will be merged or slots - //! shifted from it's siblings. - static const unsigned short leaf_slotmin = (leaf_slotmax / 2); - - //! Computed B+ tree parameter: The minimum number of key slots used - //! in an inner node. If fewer slots are used, the inner node will be - //! merged or slots shifted from it's siblings. - static const unsigned short inner_slotmin = (inner_slotmax / 2); - - //! Debug parameter: Enables expensive and thorough checking of the B+ tree - //! invariants after each insert/erase operation. - static const bool self_verify = traits::self_verify; - - //! Debug parameter: Prints out lots of debug information about how the - //! algorithms change the tree. Requires the header file to be compiled - //! with TLX_BTREE_DEBUG and the key type must be std::ostream printable. - static const bool debug = traits::debug; - - //! \} - - private: - //! \name Node Classes for In-Memory Nodes - //! \{ - - //! The header structure of each node in-memory. This structure is extended - //! by InnerNode or LeafNode. - struct node { - //! Level in the b-tree, if level == 0 -> leaf node - unsigned short level; - - //! Number of key slotuse use, so the number of valid children or data - //! pointers - unsigned short slotuse; - - //! Delayed initialisation of constructed node. - void initialize(const unsigned short l) { - level = l; - slotuse = 0; - } - - //! True if this is a leaf node. - bool is_leafnode() const { return (level == 0); } - }; - - //! Extended structure of a inner node in-memory. Contains only keys and no - //! data items. - struct InnerNode : public node { - //! Define an related allocator for the InnerNode structs. - typedef typename std::allocator_traits::template rebind_alloc alloc_type; - - //! Keys of children or data pointers - key_type slotkey[inner_slotmax]; // NOLINT - - //! Pointers to children - node *childid[inner_slotmax + 1]; // NOLINT - - //! Set variables to initial values. - void initialize(const unsigned short l) { node::initialize(l); } - - //! Return key in slot s - const key_type &key(size_t s) const { return slotkey[s]; } - - //! True if the node's slots are full. - bool is_full() const { return (node::slotuse == inner_slotmax); } - - //! True if few used entries, less than half full. - bool is_few() const { return (node::slotuse <= inner_slotmin); } - - //! True if node has too few entries. - bool is_underflow() const { return (node::slotuse < inner_slotmin); } - }; - - //! Extended structure of a leaf node in memory. Contains pairs of keys and - //! data items. Key and data slots are kept together in value_type. - struct LeafNode : public node { - //! Define an related allocator for the LeafNode structs. - typedef typename std::allocator_traits::template rebind_alloc alloc_type; - - //! Double linked list pointers to traverse the leaves - LeafNode *prev_leaf; - - //! Double linked list pointers to traverse the leaves - LeafNode *next_leaf; - - //! Array of (key, data) pairs - value_type slotdata[leaf_slotmax]; // NOLINT - - //! Set variables to initial values - void initialize() { - node::initialize(0); - prev_leaf = next_leaf = nullptr; - } - - //! Return key in slot s. - const key_type &key(size_t s) const { return key_of_value::get(slotdata[s]); } - - //! True if the node's slots are full. - bool is_full() const { return (node::slotuse == leaf_slotmax); } - - //! True if few used entries, less than half full. - bool is_few() const { return (node::slotuse <= leaf_slotmin); } - - //! True if node has too few entries. - bool is_underflow() const { return (node::slotuse < leaf_slotmin); } - - //! Set the (key,data) pair in slot. Overloaded function used by - //! bulk_load(). - void set_slot(unsigned short slot, const value_type &value) { - TLX_BTREE_ASSERT(slot < node::slotuse); - slotdata[slot] = value; - } - }; - - //! \} - - public: - //! \name Iterators and Reverse Iterators - //! \{ - - class iterator; - class const_iterator; - class reverse_iterator; - class const_reverse_iterator; - - //! STL-like iterator object for B+ tree items. The iterator points to a - //! specific slot number in a leaf. - class iterator { - public: - // *** Types - - //! The key type of the btree. Returned by key(). - typedef typename BTree::key_type key_type; - - //! The value type of the btree. Returned by operator*(). - typedef typename BTree::value_type value_type; - - //! Reference to the value_type. STL required. - typedef value_type &reference; - - //! Pointer to the value_type. STL required. - typedef value_type *pointer; - - //! STL-magic iterator category - typedef std::bidirectional_iterator_tag iterator_category; - - //! STL-magic - typedef ptrdiff_t difference_type; - - //! Our own type - typedef iterator self; - - private: - // *** Members - - //! The currently referenced leaf node of the tree - typename BTree::LeafNode *curr_leaf; - - //! Current key/data slot referenced - unsigned short curr_slot; - - //! Friendly to the const_iterator, so it may access the two data items - //! directly. - friend class const_iterator; - - //! Also friendly to the reverse_iterator, so it may access the two - //! data items directly. - friend class reverse_iterator; - - //! Also friendly to the const_reverse_iterator, so it may access the - //! two data items directly. - friend class const_reverse_iterator; - - //! Also friendly to the base btree class, because erase_iter() needs - //! to read the curr_leaf and curr_slot values directly. - friend class BTree; - - // The macro TLX_BTREE_FRIENDS can be used by outside class to access - // the B+ tree internals. This was added for wxBTreeDemo to be able to - // draw the tree. - TLX_BTREE_FRIENDS; - - public: - // *** Methods - - //! Default-Constructor of a mutable iterator - iterator() : curr_leaf(nullptr), curr_slot(0) {} - - //! Initializing-Constructor of a mutable iterator - iterator(typename BTree::LeafNode *l, unsigned short s) : curr_leaf(l), curr_slot(s) {} - - //! Copy-constructor from a reverse iterator - iterator(const reverse_iterator &it) // NOLINT - : curr_leaf(it.curr_leaf), curr_slot(it.curr_slot) {} - - //! Dereference the iterator. - reference operator*() const { return curr_leaf->slotdata[curr_slot]; } - - //! Dereference the iterator. - pointer operator->() const { return &curr_leaf->slotdata[curr_slot]; } - - //! Key of the current slot. - const key_type &key() const { return curr_leaf->key(curr_slot); } - - //! Prefix++ advance the iterator to the next slot. - iterator &operator++() { - if (curr_slot + 1u < curr_leaf->slotuse) { - ++curr_slot; - } else if (curr_leaf->next_leaf != nullptr) { - curr_leaf = curr_leaf->next_leaf; - curr_slot = 0; - } else { - // this is end() - curr_slot = curr_leaf->slotuse; - } - - return *this; - } - - //! Postfix++ advance the iterator to the next slot. - iterator operator++(int) { - iterator tmp = *this; // copy ourselves - - if (curr_slot + 1u < curr_leaf->slotuse) { - ++curr_slot; - } else if (curr_leaf->next_leaf != nullptr) { - curr_leaf = curr_leaf->next_leaf; - curr_slot = 0; - } else { - // this is end() - curr_slot = curr_leaf->slotuse; - } - - return tmp; - } - - //! Prefix-- backstep the iterator to the last slot. - iterator &operator--() { - if (curr_slot > 0) { - --curr_slot; - } else if (curr_leaf->prev_leaf != nullptr) { - curr_leaf = curr_leaf->prev_leaf; - curr_slot = curr_leaf->slotuse - 1; - } else { - // this is begin() - curr_slot = 0; - } - - return *this; - } - - //! Postfix-- backstep the iterator to the last slot. - iterator operator--(int) { - iterator tmp = *this; // copy ourselves - - if (curr_slot > 0) { - --curr_slot; - } else if (curr_leaf->prev_leaf != nullptr) { - curr_leaf = curr_leaf->prev_leaf; - curr_slot = curr_leaf->slotuse - 1; - } else { - // this is begin() - curr_slot = 0; - } - - return tmp; - } - - //! Equality of iterators. - bool operator==(const iterator &x) const { return (x.curr_leaf == curr_leaf) && (x.curr_slot == curr_slot); } - - //! Inequality of iterators. - bool operator!=(const iterator &x) const { return (x.curr_leaf != curr_leaf) || (x.curr_slot != curr_slot); } - }; - - //! STL-like read-only iterator object for B+ tree items. The iterator - //! points to a specific slot number in a leaf. - class const_iterator { - public: - // *** Types - - //! The key type of the btree. Returned by key(). - typedef typename BTree::key_type key_type; - - //! The value type of the btree. Returned by operator*(). - typedef typename BTree::value_type value_type; - - //! Reference to the value_type. STL required. - typedef const value_type &reference; - - //! Pointer to the value_type. STL required. - typedef const value_type *pointer; - - //! STL-magic iterator category - typedef std::bidirectional_iterator_tag iterator_category; - - //! STL-magic - typedef ptrdiff_t difference_type; - - //! Our own type - typedef const_iterator self; - - private: - // *** Members - - //! The currently referenced leaf node of the tree - const typename BTree::LeafNode *curr_leaf; - - //! Current key/data slot referenced - unsigned short curr_slot; - - //! Friendly to the reverse_const_iterator, so it may access the two - //! data items directly - friend class const_reverse_iterator; - - // The macro TLX_BTREE_FRIENDS can be used by outside class to access - // the B+ tree internals. This was added for wxBTreeDemo to be able to - // draw the tree. - TLX_BTREE_FRIENDS; - - public: - // *** Methods - - //! Default-Constructor of a const iterator - const_iterator() : curr_leaf(nullptr), curr_slot(0) {} - - //! Initializing-Constructor of a const iterator - const_iterator(const typename BTree::LeafNode *l, unsigned short s) : curr_leaf(l), curr_slot(s) {} - - //! Copy-constructor from a mutable iterator - const_iterator(const iterator &it) // NOLINT - : curr_leaf(it.curr_leaf), curr_slot(it.curr_slot) {} - - //! Copy-constructor from a mutable reverse iterator - const_iterator(const reverse_iterator &it) // NOLINT - : curr_leaf(it.curr_leaf), curr_slot(it.curr_slot) {} - - //! Copy-constructor from a const reverse iterator - const_iterator(const const_reverse_iterator &it) // NOLINT - : curr_leaf(it.curr_leaf), curr_slot(it.curr_slot) {} - - //! Dereference the iterator. - reference operator*() const { return curr_leaf->slotdata[curr_slot]; } - - //! Dereference the iterator. - pointer operator->() const { return &curr_leaf->slotdata[curr_slot]; } - - //! Key of the current slot. - const key_type &key() const { return curr_leaf->key(curr_slot); } - - //! Prefix++ advance the iterator to the next slot. - const_iterator &operator++() { - if (curr_slot + 1u < curr_leaf->slotuse) { - ++curr_slot; - } else if (curr_leaf->next_leaf != nullptr) { - curr_leaf = curr_leaf->next_leaf; - curr_slot = 0; - } else { - // this is end() - curr_slot = curr_leaf->slotuse; - } - - return *this; - } - - //! Postfix++ advance the iterator to the next slot. - const_iterator operator++(int) { - const_iterator tmp = *this; // copy ourselves - - if (curr_slot + 1u < curr_leaf->slotuse) { - ++curr_slot; - } else if (curr_leaf->next_leaf != nullptr) { - curr_leaf = curr_leaf->next_leaf; - curr_slot = 0; - } else { - // this is end() - curr_slot = curr_leaf->slotuse; - } - - return tmp; - } - - //! Prefix-- backstep the iterator to the last slot. - const_iterator &operator--() { - if (curr_slot > 0) { - --curr_slot; - } else if (curr_leaf->prev_leaf != nullptr) { - curr_leaf = curr_leaf->prev_leaf; - curr_slot = curr_leaf->slotuse - 1; - } else { - // this is begin() - curr_slot = 0; - } - - return *this; - } - - //! Postfix-- backstep the iterator to the last slot. - const_iterator operator--(int) { - const_iterator tmp = *this; // copy ourselves - - if (curr_slot > 0) { - --curr_slot; - } else if (curr_leaf->prev_leaf != nullptr) { - curr_leaf = curr_leaf->prev_leaf; - curr_slot = curr_leaf->slotuse - 1; - } else { - // this is begin() - curr_slot = 0; - } - - return tmp; - } - - //! Equality of iterators. - bool operator==(const const_iterator &x) const { return (x.curr_leaf == curr_leaf) && (x.curr_slot == curr_slot); } - - //! Inequality of iterators. - bool operator!=(const const_iterator &x) const { return (x.curr_leaf != curr_leaf) || (x.curr_slot != curr_slot); } - }; - - //! STL-like mutable reverse iterator object for B+ tree items. The - //! iterator points to a specific slot number in a leaf. - class reverse_iterator { - public: - // *** Types - - //! The key type of the btree. Returned by key(). - typedef typename BTree::key_type key_type; - - //! The value type of the btree. Returned by operator*(). - typedef typename BTree::value_type value_type; - - //! Reference to the value_type. STL required. - typedef value_type &reference; - - //! Pointer to the value_type. STL required. - typedef value_type *pointer; - - //! STL-magic iterator category - typedef std::bidirectional_iterator_tag iterator_category; - - //! STL-magic - typedef ptrdiff_t difference_type; - - //! Our own type - typedef reverse_iterator self; - - private: - // *** Members - - //! The currently referenced leaf node of the tree - typename BTree::LeafNode *curr_leaf; - - //! One slot past the current key/data slot referenced. - unsigned short curr_slot; - - //! Friendly to the const_iterator, so it may access the two data items - //! directly - friend class iterator; - - //! Also friendly to the const_iterator, so it may access the two data - //! items directly - friend class const_iterator; - - //! Also friendly to the const_iterator, so it may access the two data - //! items directly - friend class const_reverse_iterator; - - // The macro TLX_BTREE_FRIENDS can be used by outside class to access - // the B+ tree internals. This was added for wxBTreeDemo to be able to - // draw the tree. - TLX_BTREE_FRIENDS; - - public: - // *** Methods - - //! Default-Constructor of a reverse iterator - reverse_iterator() : curr_leaf(nullptr), curr_slot(0) {} - - //! Initializing-Constructor of a mutable reverse iterator - reverse_iterator(typename BTree::LeafNode *l, unsigned short s) : curr_leaf(l), curr_slot(s) {} - - //! Copy-constructor from a mutable iterator - reverse_iterator(const iterator &it) // NOLINT - : curr_leaf(it.curr_leaf), curr_slot(it.curr_slot) {} - - //! Dereference the iterator. - reference operator*() const { - TLX_BTREE_ASSERT(curr_slot > 0); - return curr_leaf->slotdata[curr_slot - 1]; - } - - //! Dereference the iterator. - pointer operator->() const { - TLX_BTREE_ASSERT(curr_slot > 0); - return &curr_leaf->slotdata[curr_slot - 1]; - } - - //! Key of the current slot. - const key_type &key() const { - TLX_BTREE_ASSERT(curr_slot > 0); - return curr_leaf->key(curr_slot - 1); - } - - //! Prefix++ advance the iterator to the next slot. - reverse_iterator &operator++() { - if (curr_slot > 1) { - --curr_slot; - } else if (curr_leaf->prev_leaf != nullptr) { - curr_leaf = curr_leaf->prev_leaf; - curr_slot = curr_leaf->slotuse; - } else { - // this is begin() == rend() - curr_slot = 0; - } - - return *this; - } - - //! Postfix++ advance the iterator to the next slot. - reverse_iterator operator++(int) { - reverse_iterator tmp = *this; // copy ourselves - - if (curr_slot > 1) { - --curr_slot; - } else if (curr_leaf->prev_leaf != nullptr) { - curr_leaf = curr_leaf->prev_leaf; - curr_slot = curr_leaf->slotuse; - } else { - // this is begin() == rend() - curr_slot = 0; - } - - return tmp; - } - - //! Prefix-- backstep the iterator to the last slot. - reverse_iterator &operator--() { - if (curr_slot < curr_leaf->slotuse) { - ++curr_slot; - } else if (curr_leaf->next_leaf != nullptr) { - curr_leaf = curr_leaf->next_leaf; - curr_slot = 1; - } else { - // this is end() == rbegin() - curr_slot = curr_leaf->slotuse; - } - - return *this; - } - - //! Postfix-- backstep the iterator to the last slot. - reverse_iterator operator--(int) { - reverse_iterator tmp = *this; // copy ourselves - - if (curr_slot < curr_leaf->slotuse) { - ++curr_slot; - } else if (curr_leaf->next_leaf != nullptr) { - curr_leaf = curr_leaf->next_leaf; - curr_slot = 1; - } else { - // this is end() == rbegin() - curr_slot = curr_leaf->slotuse; - } - - return tmp; - } - - //! Equality of iterators. - bool operator==(const reverse_iterator &x) const { - return (x.curr_leaf == curr_leaf) && (x.curr_slot == curr_slot); - } - - //! Inequality of iterators. - bool operator!=(const reverse_iterator &x) const { - return (x.curr_leaf != curr_leaf) || (x.curr_slot != curr_slot); - } - }; - - //! STL-like read-only reverse iterator object for B+ tree items. The - //! iterator points to a specific slot number in a leaf. - class const_reverse_iterator { - public: - // *** Types - - //! The key type of the btree. Returned by key(). - typedef typename BTree::key_type key_type; - - //! The value type of the btree. Returned by operator*(). - typedef typename BTree::value_type value_type; - - //! Reference to the value_type. STL required. - typedef const value_type &reference; - - //! Pointer to the value_type. STL required. - typedef const value_type *pointer; - - //! STL-magic iterator category - typedef std::bidirectional_iterator_tag iterator_category; - - //! STL-magic - typedef ptrdiff_t difference_type; - - //! Our own type - typedef const_reverse_iterator self; - - private: - // *** Members - - //! The currently referenced leaf node of the tree - const typename BTree::LeafNode *curr_leaf; - - //! One slot past the current key/data slot referenced. - unsigned short curr_slot; - - //! Friendly to the const_iterator, so it may access the two data items - //! directly. - friend class reverse_iterator; - - // The macro TLX_BTREE_FRIENDS can be used by outside class to access - // the B+ tree internals. This was added for wxBTreeDemo to be able to - // draw the tree. - TLX_BTREE_FRIENDS; - - public: - // *** Methods - - //! Default-Constructor of a const reverse iterator. - const_reverse_iterator() : curr_leaf(nullptr), curr_slot(0) {} - - //! Initializing-Constructor of a const reverse iterator. - const_reverse_iterator(const typename BTree::LeafNode *l, unsigned short s) : curr_leaf(l), curr_slot(s) {} - - //! Copy-constructor from a mutable iterator. - const_reverse_iterator(const iterator &it) // NOLINT - : curr_leaf(it.curr_leaf), curr_slot(it.curr_slot) {} - - //! Copy-constructor from a const iterator. - const_reverse_iterator(const const_iterator &it) // NOLINT - : curr_leaf(it.curr_leaf), curr_slot(it.curr_slot) {} - - //! Copy-constructor from a mutable reverse iterator. - const_reverse_iterator(const reverse_iterator &it) // NOLINT - : curr_leaf(it.curr_leaf), curr_slot(it.curr_slot) {} - - //! Dereference the iterator. - reference operator*() const { - TLX_BTREE_ASSERT(curr_slot > 0); - return curr_leaf->slotdata[curr_slot - 1]; - } - - //! Dereference the iterator. - pointer operator->() const { - TLX_BTREE_ASSERT(curr_slot > 0); - return &curr_leaf->slotdata[curr_slot - 1]; - } - - //! Key of the current slot. - const key_type &key() const { - TLX_BTREE_ASSERT(curr_slot > 0); - return curr_leaf->key(curr_slot - 1); - } - - //! Prefix++ advance the iterator to the previous slot. - const_reverse_iterator &operator++() { - if (curr_slot > 1) { - --curr_slot; - } else if (curr_leaf->prev_leaf != nullptr) { - curr_leaf = curr_leaf->prev_leaf; - curr_slot = curr_leaf->slotuse; - } else { - // this is begin() == rend() - curr_slot = 0; - } - - return *this; - } - - //! Postfix++ advance the iterator to the previous slot. - const_reverse_iterator operator++(int) { - const_reverse_iterator tmp = *this; // copy ourselves - - if (curr_slot > 1) { - --curr_slot; - } else if (curr_leaf->prev_leaf != nullptr) { - curr_leaf = curr_leaf->prev_leaf; - curr_slot = curr_leaf->slotuse; - } else { - // this is begin() == rend() - curr_slot = 0; - } - - return tmp; - } - - //! Prefix-- backstep the iterator to the next slot. - const_reverse_iterator &operator--() { - if (curr_slot < curr_leaf->slotuse) { - ++curr_slot; - } else if (curr_leaf->next_leaf != nullptr) { - curr_leaf = curr_leaf->next_leaf; - curr_slot = 1; - } else { - // this is end() == rbegin() - curr_slot = curr_leaf->slotuse; - } - - return *this; - } - - //! Postfix-- backstep the iterator to the next slot. - const_reverse_iterator operator--(int) { - const_reverse_iterator tmp = *this; // copy ourselves - - if (curr_slot < curr_leaf->slotuse) { - ++curr_slot; - } else if (curr_leaf->next_leaf != nullptr) { - curr_leaf = curr_leaf->next_leaf; - curr_slot = 1; - } else { - // this is end() == rbegin() - curr_slot = curr_leaf->slotuse; - } - - return tmp; - } - - //! Equality of iterators. - bool operator==(const const_reverse_iterator &x) const { - return (x.curr_leaf == curr_leaf) && (x.curr_slot == curr_slot); - } - - //! Inequality of iterators. - bool operator!=(const const_reverse_iterator &x) const { - return (x.curr_leaf != curr_leaf) || (x.curr_slot != curr_slot); - } - }; - - //! \} - - public: - //! \name Small Statistics Structure - //! \{ - - /*! - * A small struct containing basic statistics about the B+ tree. It can be - * fetched using get_stats(). - */ - struct tree_stats { - //! Number of items in the B+ tree - size_type size; - - //! Number of leaves in the B+ tree - size_type leaves; - - //! Number of inner nodes in the B+ tree - size_type inner_nodes; - - //! Base B+ tree parameter: The number of key/data slots in each leaf - static const unsigned short leaf_slots = Self::leaf_slotmax; - - //! Base B+ tree parameter: The number of key slots in each inner node. - static const unsigned short inner_slots = Self::inner_slotmax; - - //! Zero initialized - tree_stats() : size(0), leaves(0), inner_nodes(0) {} - - //! Return the total number of nodes - size_type nodes() const { return inner_nodes + leaves; } - - //! Return the average fill of leaves - double avgfill_leaves() const { return static_cast(size) / (leaves * leaf_slots); } - }; - - //! \} - - private: - //! \name Tree Object Data Members - //! \{ - - //! Pointer to the B+ tree's root node, either leaf or inner node. - node *root_; - - //! Pointer to first leaf in the double linked leaf chain. - LeafNode *head_leaf_; - - //! Pointer to last leaf in the double linked leaf chain. - LeafNode *tail_leaf_; - - //! Other small statistics about the B+ tree. - tree_stats stats_; - - //! Key comparison object. More comparison functions are generated from - //! this < relation. - key_compare key_less_; - - //! Memory allocator. - allocator_type allocator_; - - //! \} - - public: - //! \name Constructors and Destructor - //! \{ - - //! Default constructor initializing an empty B+ tree with the standard key - //! comparison function. - explicit BTree(const allocator_type &alloc = allocator_type()) - : root_(nullptr), head_leaf_(nullptr), tail_leaf_(nullptr), allocator_(alloc) {} - - //! Constructor initializing an empty B+ tree with a special key - //! comparison object. - explicit BTree(const key_compare &kcf, const allocator_type &alloc = allocator_type()) - : root_(nullptr), head_leaf_(nullptr), tail_leaf_(nullptr), key_less_(kcf), allocator_(alloc) {} - - //! Constructor initializing a B+ tree with the range [first,last). The - //! range need not be sorted. To create a B+ tree from a sorted range, use - //! bulk_load(). - template - BTree(InputIterator first, InputIterator last, const allocator_type &alloc = allocator_type()) - : root_(nullptr), head_leaf_(nullptr), tail_leaf_(nullptr), allocator_(alloc) { - insert(first, last); - } - - //! Constructor initializing a B+ tree with the range [first,last) and a - //! special key comparison object. The range need not be sorted. To create - //! a B+ tree from a sorted range, use bulk_load(). - template - BTree(InputIterator first, InputIterator last, const key_compare &kcf, const allocator_type &alloc = allocator_type()) - : root_(nullptr), head_leaf_(nullptr), tail_leaf_(nullptr), key_less_(kcf), allocator_(alloc) { - insert(first, last); - } - - //! Frees up all used B+ tree memory pages - ~BTree() { clear(); } - - //! Fast swapping of two identical B+ tree objects. - void swap(BTree &from) { - std::swap(root_, from.root_); - std::swap(head_leaf_, from.head_leaf_); - std::swap(tail_leaf_, from.tail_leaf_); - std::swap(stats_, from.stats_); - std::swap(key_less_, from.key_less_); - std::swap(allocator_, from.allocator_); - } - - //! \} - - public: - //! \name Key and Value Comparison Function Objects - //! \{ - - //! Function class to compare value_type objects. Required by the STL - class value_compare { - protected: - //! Key comparison function from the template parameter - key_compare key_comp; - - //! Constructor called from BTree::value_comp() - explicit value_compare(key_compare kc) : key_comp(kc) {} - - //! Friendly to the btree class so it may call the constructor - friend class BTree; - - public: - //! Function call "less"-operator resulting in true if x < y. - bool operator()(const value_type &x, const value_type &y) const { return key_comp(x.first, y.first); } - }; - - //! Constant access to the key comparison object sorting the B+ tree. - key_compare key_comp() const { return key_less_; } - - //! Constant access to a constructed value_type comparison object. Required - //! by the STL. - value_compare value_comp() const { return value_compare(key_less_); } - - //! \} - - private: - //! \name Convenient Key Comparison Functions Generated From key_less - //! \{ - - //! True if a < b ? "constructed" from key_less_() - bool key_less(const key_type &a, const key_type &b) const { return key_less_(a, b); } - - //! True if a <= b ? constructed from key_less() - bool key_lessequal(const key_type &a, const key_type &b) const { return !key_less_(b, a); } - - //! True if a > b ? constructed from key_less() - bool key_greater(const key_type &a, const key_type &b) const { return key_less_(b, a); } - - //! True if a >= b ? constructed from key_less() - bool key_greaterequal(const key_type &a, const key_type &b) const { return !key_less_(a, b); } - - //! True if a == b ? constructed from key_less(). This requires the < - //! relation to be a total order, otherwise the B+ tree cannot be sorted. - bool key_equal(const key_type &a, const key_type &b) const { return !key_less_(a, b) && !key_less_(b, a); } - - //! \} - - public: - //! \name Allocators - //! \{ - - //! Return the base node allocator provided during construction. - allocator_type get_allocator() const { return allocator_; } - - //! \} - - private: - //! \name Node Object Allocation and Deallocation Functions - //! \{ - - //! Return an allocator for LeafNode objects. - typename LeafNode::alloc_type leaf_node_allocator() { return typename LeafNode::alloc_type(allocator_); } - - //! Return an allocator for InnerNode objects. - typename InnerNode::alloc_type inner_node_allocator() { return typename InnerNode::alloc_type(allocator_); } - - //! Allocate and initialize a leaf node - LeafNode *allocate_leaf() { - LeafNode *n = new (leaf_node_allocator().allocate(1)) LeafNode(); - n->initialize(); - stats_.leaves++; - return n; - } - - //! Allocate and initialize an inner node - InnerNode *allocate_inner(unsigned short level) { - InnerNode *n = new (inner_node_allocator().allocate(1)) InnerNode(); - n->initialize(level); - stats_.inner_nodes++; - return n; - } - - //! Correctly free either inner or leaf node, destructs all contained key - //! and value objects. - void free_node(node *n) { - if (n->is_leafnode()) { - LeafNode *ln = static_cast(n); - typename LeafNode::alloc_type a(leaf_node_allocator()); - std::allocator_traits::destroy(a, ln); - std::allocator_traits::deallocate(a, ln, 1); - stats_.leaves--; - } else { - InnerNode *in = static_cast(n); - typename InnerNode::alloc_type a(inner_node_allocator()); - std::allocator_traits::destroy(a, in); - std::allocator_traits::deallocate(a, in, 1); - stats_.inner_nodes--; - } - } - - //! \} - - public: - //! \name Fast Destruction of the B+ Tree - //! \{ - - //! Frees all key/data pairs and all nodes of the tree. - void clear() { - if (root_) { - clear_recursive(root_); - free_node(root_); - - root_ = nullptr; - head_leaf_ = tail_leaf_ = nullptr; - - stats_ = tree_stats(); - } - - TLX_BTREE_ASSERT(stats_.size == 0); - } - - private: - //! Recursively free up nodes. - void clear_recursive(node *n) { - if (n->is_leafnode()) { - LeafNode *leafnode = static_cast(n); - - for (unsigned short slot = 0; slot < leafnode->slotuse; ++slot) { - // data objects are deleted by LeafNode's destructor - } - } else { - InnerNode *innernode = static_cast(n); - - for (unsigned short slot = 0; slot < innernode->slotuse + 1; ++slot) { - clear_recursive(innernode->childid[slot]); - free_node(innernode->childid[slot]); - } - } - } - - //! \} - - public: - //! \name STL Iterator Construction Functions - //! \{ - - //! Constructs a read/data-write iterator that points to the first slot in - //! the first leaf of the B+ tree. - iterator begin() { return iterator(head_leaf_, 0); } - - //! Constructs a read/data-write iterator that points to the first invalid - //! slot in the last leaf of the B+ tree. - iterator end() { return iterator(tail_leaf_, tail_leaf_ ? tail_leaf_->slotuse : 0); } - - //! Constructs a read-only constant iterator that points to the first slot - //! in the first leaf of the B+ tree. - const_iterator begin() const { return const_iterator(head_leaf_, 0); } - - //! Constructs a read-only constant iterator that points to the first - //! invalid slot in the last leaf of the B+ tree. - const_iterator end() const { return const_iterator(tail_leaf_, tail_leaf_ ? tail_leaf_->slotuse : 0); } - - //! Constructs a read/data-write reverse iterator that points to the first - //! invalid slot in the last leaf of the B+ tree. Uses STL magic. - reverse_iterator rbegin() { return reverse_iterator(end()); } - - //! Constructs a read/data-write reverse iterator that points to the first - //! slot in the first leaf of the B+ tree. Uses STL magic. - reverse_iterator rend() { return reverse_iterator(begin()); } - - //! Constructs a read-only reverse iterator that points to the first - //! invalid slot in the last leaf of the B+ tree. Uses STL magic. - const_reverse_iterator rbegin() const { return const_reverse_iterator(end()); } - - //! Constructs a read-only reverse iterator that points to the first slot - //! in the first leaf of the B+ tree. Uses STL magic. - const_reverse_iterator rend() const { return const_reverse_iterator(begin()); } - - //! \} - - private: - //! \name B+ Tree Node Binary Search Functions - //! \{ - - //! Searches for the first key in the node n greater or equal to key. Uses - //! binary search with an optional linear self-verification. This is a - //! template function, because the slotkey array is located at different - //! places in LeafNode and InnerNode. - template - unsigned short find_lower(const node_type *n, const key_type &key) const { - if (sizeof(*n) > traits::binsearch_threshold) { - if (n->slotuse == 0) return 0; - - unsigned short lo = 0, hi = n->slotuse; - - while (lo < hi) { - unsigned short mid = (lo + hi) >> 1; - - if (key_lessequal(key, n->key(mid))) { - hi = mid; // key <= mid - } else { - lo = mid + 1; // key > mid - } - } - - TLX_BTREE_PRINT("BTree::find_lower: on " << n << " key " << key << " -> " << lo << " / " << hi); - - // verify result using simple linear search - if (self_verify) { - unsigned short i = 0; - while (i < n->slotuse && key_less(n->key(i), key)) ++i; - - TLX_BTREE_PRINT("BTree::find_lower: testfind: " << i); - TLX_BTREE_ASSERT(i == lo); - } - - return lo; - } else // for nodes <= binsearch_threshold do linear search. - { - unsigned short lo = 0; - while (lo < n->slotuse && key_less(n->key(lo), key)) ++lo; - return lo; - } - } - - //! Searches for the first key in the node n greater than key. Uses binary - //! search with an optional linear self-verification. This is a template - //! function, because the slotkey array is located at different places in - //! LeafNode and InnerNode. - template - unsigned short find_upper(const node_type *n, const key_type &key) const { - if (sizeof(*n) > traits::binsearch_threshold) { - if (n->slotuse == 0) return 0; - - unsigned short lo = 0, hi = n->slotuse; - - while (lo < hi) { - unsigned short mid = (lo + hi) >> 1; - - if (key_less(key, n->key(mid))) { - hi = mid; // key < mid - } else { - lo = mid + 1; // key >= mid - } - } - - TLX_BTREE_PRINT("BTree::find_upper: on " << n << " key " << key << " -> " << lo << " / " << hi); - - // verify result using simple linear search - if (self_verify) { - unsigned short i = 0; - while (i < n->slotuse && key_lessequal(n->key(i), key)) ++i; - - TLX_BTREE_PRINT("BTree::find_upper testfind: " << i); - TLX_BTREE_ASSERT(i == hi); - } - - return lo; - } else // for nodes <= binsearch_threshold do linear search. - { - unsigned short lo = 0; - while (lo < n->slotuse && key_lessequal(n->key(lo), key)) ++lo; - return lo; - } - } - - //! \} - - public: - //! \name Access Functions to the Item Count - //! \{ - - //! Return the number of key/data pairs in the B+ tree - size_type size() const { return stats_.size; } - - //! Returns true if there is at least one key/data pair in the B+ tree - bool empty() const { return (size() == size_type(0)); } - - //! Returns the largest possible size of the B+ Tree. This is just a - //! function required by the STL standard, the B+ Tree can hold more items. - size_type max_size() const { return size_type(-1); } - - //! Return a const reference to the current statistics. - const struct tree_stats &get_stats() const { return stats_; } - - //! \} - - public: - //! \name STL Access Functions Querying the Tree by Descending to a Leaf - //! \{ - - //! Non-STL function checking whether a key is in the B+ tree. The same as - //! (find(k) != end()) or (count() != 0). - bool exists(const key_type &key) const { - const node *n = root_; - if (!n) return false; - - while (!n->is_leafnode()) { - const InnerNode *inner = static_cast(n); - unsigned short slot = find_lower(inner, key); - - n = inner->childid[slot]; - } - - const LeafNode *leaf = static_cast(n); - - unsigned short slot = find_lower(leaf, key); - return (slot < leaf->slotuse && key_equal(key, leaf->key(slot))); - } - - //! Tries to locate a key in the B+ tree and returns an iterator to the - //! key/data slot if found. If unsuccessful it returns end(). - iterator find(const key_type &key) { - node *n = root_; - if (!n) return end(); - - while (!n->is_leafnode()) { - const InnerNode *inner = static_cast(n); - unsigned short slot = find_lower(inner, key); - - n = inner->childid[slot]; - } - - LeafNode *leaf = static_cast(n); - - unsigned short slot = find_lower(leaf, key); - return (slot < leaf->slotuse && key_equal(key, leaf->key(slot))) ? iterator(leaf, slot) : end(); - } - - //! Tries to locate a key in the B+ tree and returns an constant iterator to - //! the key/data slot if found. If unsuccessful it returns end(). - const_iterator find(const key_type &key) const { - const node *n = root_; - if (!n) return end(); - - while (!n->is_leafnode()) { - const InnerNode *inner = static_cast(n); - unsigned short slot = find_lower(inner, key); - - n = inner->childid[slot]; - } - - const LeafNode *leaf = static_cast(n); - - unsigned short slot = find_lower(leaf, key); - return (slot < leaf->slotuse && key_equal(key, leaf->key(slot))) ? const_iterator(leaf, slot) : end(); - } - - //! Tries to locate a key in the B+ tree and returns the number of identical - //! key entries found. - size_type count(const key_type &key) const { - const node *n = root_; - if (!n) return 0; - - while (!n->is_leafnode()) { - const InnerNode *inner = static_cast(n); - unsigned short slot = find_lower(inner, key); - - n = inner->childid[slot]; - } - - const LeafNode *leaf = static_cast(n); - - unsigned short slot = find_lower(leaf, key); - size_type num = 0; - - while (leaf && slot < leaf->slotuse && key_equal(key, leaf->key(slot))) { - ++num; - if (++slot >= leaf->slotuse) { - leaf = leaf->next_leaf; - slot = 0; - } - } - - return num; - } - - //! Searches the B+ tree and returns an iterator to the first pair equal to - //! or greater than key, or end() if all keys are smaller. - iterator lower_bound(const key_type &key) { - node *n = root_; - if (!n) return end(); - - while (!n->is_leafnode()) { - const InnerNode *inner = static_cast(n); - unsigned short slot = find_lower(inner, key); - - n = inner->childid[slot]; - } - - LeafNode *leaf = static_cast(n); - - unsigned short slot = find_lower(leaf, key); - return iterator(leaf, slot); - } - - //! Searches the B+ tree and returns a constant iterator to the first pair - //! equal to or greater than key, or end() if all keys are smaller. - const_iterator lower_bound(const key_type &key) const { - const node *n = root_; - if (!n) return end(); - - while (!n->is_leafnode()) { - const InnerNode *inner = static_cast(n); - unsigned short slot = find_lower(inner, key); - - n = inner->childid[slot]; - } - - const LeafNode *leaf = static_cast(n); - - unsigned short slot = find_lower(leaf, key); - return const_iterator(leaf, slot); - } - - //! Searches the B+ tree and returns an iterator to the first pair greater - //! than key, or end() if all keys are smaller or equal. - iterator upper_bound(const key_type &key) { - node *n = root_; - if (!n) return end(); - - while (!n->is_leafnode()) { - const InnerNode *inner = static_cast(n); - unsigned short slot = find_upper(inner, key); - - n = inner->childid[slot]; - } - - LeafNode *leaf = static_cast(n); - - unsigned short slot = find_upper(leaf, key); - return iterator(leaf, slot); - } - - //! Searches the B+ tree and returns a constant iterator to the first pair - //! greater than key, or end() if all keys are smaller or equal. - const_iterator upper_bound(const key_type &key) const { - const node *n = root_; - if (!n) return end(); - - while (!n->is_leafnode()) { - const InnerNode *inner = static_cast(n); - unsigned short slot = find_upper(inner, key); - - n = inner->childid[slot]; - } - - const LeafNode *leaf = static_cast(n); - - unsigned short slot = find_upper(leaf, key); - return const_iterator(leaf, slot); - } - - //! Searches the B+ tree and returns both lower_bound() and upper_bound(). - std::pair equal_range(const key_type &key) { - return std::pair(lower_bound(key), upper_bound(key)); - } - - //! Searches the B+ tree and returns both lower_bound() and upper_bound(). - std::pair equal_range(const key_type &key) const { - return std::pair(lower_bound(key), upper_bound(key)); - } - - //! \} - - public: - //! \name B+ Tree Object Comparison Functions - //! \{ - - //! Equality relation of B+ trees of the same type. B+ trees of the same - //! size and equal elements (both key and data) are considered equal. Beware - //! of the random ordering of duplicate keys. - bool operator==(const BTree &other) const { - return (size() == other.size()) && std::equal(begin(), end(), other.begin()); - } - - //! Inequality relation. Based on operator==. - bool operator!=(const BTree &other) const { return !(*this == other); } - - //! Total ordering relation of B+ trees of the same type. It uses - //! std::lexicographical_compare() for the actual comparison of elements. - bool operator<(const BTree &other) const { - return std::lexicographical_compare(begin(), end(), other.begin(), other.end()); - } - - //! Greater relation. Based on operator<. - bool operator>(const BTree &other) const { return other < *this; } - - //! Less-equal relation. Based on operator<. - bool operator<=(const BTree &other) const { return !(other < *this); } - - //! Greater-equal relation. Based on operator<. - bool operator>=(const BTree &other) const { return !(*this < other); } - - //! \} - - public: - //! \name Fast Copy: Assign Operator and Copy Constructors - //! \{ - - //! Assignment operator. All the key/data pairs are copied. - BTree &operator=(const BTree &other) { - if (this != &other) { - clear(); - - key_less_ = other.key_comp(); - allocator_ = other.get_allocator(); - - if (other.size() != 0) { - stats_.leaves = stats_.inner_nodes = 0; - if (other.root_) { - root_ = copy_recursive(other.root_); - } - stats_ = other.stats_; - } - - if (self_verify) verify(); - } - return *this; - } - - //! Copy constructor. The newly initialized B+ tree object will contain a - //! copy of all key/data pairs. - BTree(const BTree &other) - : root_(nullptr), - head_leaf_(nullptr), - tail_leaf_(nullptr), - stats_(other.stats_), - key_less_(other.key_comp()), - allocator_(other.get_allocator()) { - if (size() > 0) { - stats_.leaves = stats_.inner_nodes = 0; - if (other.root_) { - root_ = copy_recursive(other.root_); - } - if (self_verify) verify(); - } - } - - private: - //! Recursively copy nodes from another B+ tree object - struct node *copy_recursive(const node *n) { - if (n->is_leafnode()) { - const LeafNode *leaf = static_cast(n); - LeafNode *newleaf = allocate_leaf(); - - newleaf->slotuse = leaf->slotuse; - std::copy(leaf->slotdata, leaf->slotdata + leaf->slotuse, newleaf->slotdata); - - if (head_leaf_ == nullptr) { - head_leaf_ = tail_leaf_ = newleaf; - newleaf->prev_leaf = newleaf->next_leaf = nullptr; - } else { - newleaf->prev_leaf = tail_leaf_; - tail_leaf_->next_leaf = newleaf; - tail_leaf_ = newleaf; - } - - return newleaf; - } else { - const InnerNode *inner = static_cast(n); - InnerNode *newinner = allocate_inner(inner->level); - - newinner->slotuse = inner->slotuse; - std::copy(inner->slotkey, inner->slotkey + inner->slotuse, newinner->slotkey); - - for (unsigned short slot = 0; slot <= inner->slotuse; ++slot) { - newinner->childid[slot] = copy_recursive(inner->childid[slot]); - } - - return newinner; - } - } - - //! \} - - public: - //! \name Public Insertion Functions - //! \{ - - //! Attempt to insert a key/data pair into the B+ tree. If the tree does not - //! allow duplicate keys, then the insert may fail if it is already present. - std::pair insert(const value_type &x) { return insert_start(key_of_value::get(x), x); } - - //! Attempt to insert a key/data pair into the B+ tree. The iterator hint is - //! currently ignored by the B+ tree insertion routine. - iterator insert(iterator /* hint */, const value_type &x) { return insert_start(key_of_value::get(x), x).first; } - - //! Attempt to insert the range [first,last) of value_type pairs into the B+ - //! tree. Each key/data pair is inserted individually; to bulk load the - //! tree, use a constructor with range. - template - void insert(InputIterator first, InputIterator last) { - InputIterator iter = first; - while (iter != last) { - insert(*iter); - ++iter; - } - } - - //! \} - - private: - //! \name Private Insertion Functions - //! \{ - - //! Start the insertion descent at the current root and handle root splits. - //! Returns true if the item was inserted - std::pair insert_start(const key_type &key, const value_type &value) { - node *newchild = nullptr; - key_type newkey = key_type(); - - if (root_ == nullptr) { - root_ = head_leaf_ = tail_leaf_ = allocate_leaf(); - } - - std::pair r = insert_descend(root_, key, value, &newkey, &newchild); - - if (newchild) { - // this only occurs if insert_descend() could not insert the key - // into the root node, this mean the root is full and a new root - // needs to be created. - InnerNode *newroot = allocate_inner(root_->level + 1); - newroot->slotkey[0] = newkey; - - newroot->childid[0] = root_; - newroot->childid[1] = newchild; - - newroot->slotuse = 1; - - root_ = newroot; - } - - // increment size if the item was inserted - if (r.second) ++stats_.size; - -#ifdef TLX_BTREE_DEBUG - if (debug) print(std::cout); -#endif - - if (self_verify) { - verify(); - TLX_BTREE_ASSERT(exists(key)); - } - - return r; - } - - /*! - * Insert an item into the B+ tree. - * - * Descend down the nodes to a leaf, insert the key/data pair in a free - * slot. If the node overflows, then it must be split and the new split node - * inserted into the parent. Unroll / this splitting up to the root. - */ - std::pair insert_descend(node *n, const key_type &key, const value_type &value, key_type *splitkey, - node **splitnode) { - if (!n->is_leafnode()) { - InnerNode *inner = static_cast(n); - - key_type newkey = key_type(); - node *newchild = nullptr; - - unsigned short slot = find_lower(inner, key); - - TLX_BTREE_PRINT("BTree::insert_descend into " << inner->childid[slot]); - - std::pair r = insert_descend(inner->childid[slot], key, value, &newkey, &newchild); - - if (newchild) { - TLX_BTREE_PRINT("BTree::insert_descend newchild" - << " with key " << newkey << " node " << newchild << " at slot " << slot); - - if (inner->is_full()) { - split_inner_node(inner, splitkey, splitnode, slot); - - TLX_BTREE_PRINT("BTree::insert_descend done split_inner:" - << " putslot: " << slot << " putkey: " << newkey << " upkey: " << *splitkey); - -#ifdef TLX_BTREE_DEBUG - if (debug) { - print_node(std::cout, inner); - print_node(std::cout, *splitnode); - } -#endif - - // check if insert slot is in the split sibling node - TLX_BTREE_PRINT("BTree::insert_descend switch: " << slot << " > " << inner->slotuse + 1); - - if (slot == inner->slotuse + 1 && inner->slotuse < (*splitnode)->slotuse) { - // special case when the insert slot matches the split - // place between the two nodes, then the insert key - // becomes the split key. - - TLX_BTREE_ASSERT(inner->slotuse + 1 < inner_slotmax); - - InnerNode *split = static_cast(*splitnode); - - // move the split key and it's datum into the left node - inner->slotkey[inner->slotuse] = *splitkey; - inner->childid[inner->slotuse + 1] = split->childid[0]; - inner->slotuse++; - - // set new split key and move corresponding datum into - // right node - split->childid[0] = newchild; - *splitkey = newkey; - - return r; - } else if (slot >= inner->slotuse + 1) { - // in case the insert slot is in the newly create split - // node, we reuse the code below. - - slot -= inner->slotuse + 1; - inner = static_cast(*splitnode); - TLX_BTREE_PRINT( - "BTree::insert_descend switching to " - "splitted node " - << inner << " slot " << slot); - } - } - - // move items and put pointer to child node into correct slot - TLX_BTREE_ASSERT(slot >= 0 && slot <= inner->slotuse); - - std::copy_backward(inner->slotkey + slot, inner->slotkey + inner->slotuse, inner->slotkey + inner->slotuse + 1); - std::copy_backward(inner->childid + slot, inner->childid + inner->slotuse + 1, - inner->childid + inner->slotuse + 2); - - inner->slotkey[slot] = newkey; - inner->childid[slot + 1] = newchild; - inner->slotuse++; - } - - return r; - } else // n->is_leafnode() == true - { - LeafNode *leaf = static_cast(n); - - unsigned short slot = find_lower(leaf, key); - - if (!allow_duplicates && slot < leaf->slotuse && key_equal(key, leaf->key(slot))) { - return std::pair(iterator(leaf, slot), false); - } - - if (leaf->is_full()) { - split_leaf_node(leaf, splitkey, splitnode); - - // check if insert slot is in the split sibling node - if (slot >= leaf->slotuse) { - slot -= leaf->slotuse; - leaf = static_cast(*splitnode); - } - } - - // move items and put data item into correct data slot - TLX_BTREE_ASSERT(slot >= 0 && slot <= leaf->slotuse); - - std::copy_backward(leaf->slotdata + slot, leaf->slotdata + leaf->slotuse, leaf->slotdata + leaf->slotuse + 1); - - leaf->slotdata[slot] = value; - leaf->slotuse++; - - if (splitnode && leaf != *splitnode && slot == leaf->slotuse - 1) { - // special case: the node was split, and the insert is at the - // last slot of the old node. then the splitkey must be updated. - *splitkey = key; - } - - return std::pair(iterator(leaf, slot), true); - } - } - - //! Split up a leaf node into two equally-filled sibling leaves. Returns the - //! new nodes and it's insertion key in the two parameters. - void split_leaf_node(LeafNode *leaf, key_type *out_newkey, node **out_newleaf) { - TLX_BTREE_ASSERT(leaf->is_full()); - - unsigned short mid = (leaf->slotuse >> 1); - - TLX_BTREE_PRINT("BTree::split_leaf_node on " << leaf); - - LeafNode *newleaf = allocate_leaf(); - - newleaf->slotuse = leaf->slotuse - mid; - - newleaf->next_leaf = leaf->next_leaf; - if (newleaf->next_leaf == nullptr) { - TLX_BTREE_ASSERT(leaf == tail_leaf_); - tail_leaf_ = newleaf; - } else { - newleaf->next_leaf->prev_leaf = newleaf; - } - - std::copy(leaf->slotdata + mid, leaf->slotdata + leaf->slotuse, newleaf->slotdata); - - leaf->slotuse = mid; - leaf->next_leaf = newleaf; - newleaf->prev_leaf = leaf; - - *out_newkey = leaf->key(leaf->slotuse - 1); - *out_newleaf = newleaf; - } - - //! Split up an inner node into two equally-filled sibling nodes. Returns - //! the new nodes and it's insertion key in the two parameters. Requires the - //! slot of the item will be inserted, so the nodes will be the same size - //! after the insert. - void split_inner_node(InnerNode *inner, key_type *out_newkey, node **out_newinner, unsigned int addslot) { - TLX_BTREE_ASSERT(inner->is_full()); - - unsigned short mid = (inner->slotuse >> 1); - - TLX_BTREE_PRINT("BTree::split_inner: mid " << mid << " addslot " << addslot); - - // if the split is uneven and the overflowing item will be put into the - // larger node, then the smaller split node may underflow - if (addslot <= mid && mid > inner->slotuse - (mid + 1)) mid--; - - TLX_BTREE_PRINT("BTree::split_inner: mid " << mid << " addslot " << addslot); - - TLX_BTREE_PRINT("BTree::split_inner_node on " << inner << " into two nodes " << mid << " and " - << inner->slotuse - (mid + 1) << " sized"); - - InnerNode *newinner = allocate_inner(inner->level); - - newinner->slotuse = inner->slotuse - (mid + 1); - - std::copy(inner->slotkey + mid + 1, inner->slotkey + inner->slotuse, newinner->slotkey); - std::copy(inner->childid + mid + 1, inner->childid + inner->slotuse + 1, newinner->childid); - - inner->slotuse = mid; - - *out_newkey = inner->key(mid); - *out_newinner = newinner; - } - - //! \} - - public: - //! \name Bulk Loader - Construct Tree from Sorted Sequence - //! \{ - - //! Bulk load a sorted range. Loads items into leaves and constructs a - //! B-tree above them. The tree must be empty when calling this function. - template - void bulk_load(Iterator ibegin, Iterator iend) { - TLX_BTREE_ASSERT(empty()); - - stats_.size = iend - ibegin; - - // calculate number of leaves needed, round up. - size_t num_items = iend - ibegin; - size_t num_leaves = (num_items + leaf_slotmax - 1) / leaf_slotmax; - - TLX_BTREE_PRINT("BTree::bulk_load, level 0: " - << stats_.size << " items into " << num_leaves << " leaves with up to " - << ((iend - ibegin + num_leaves - 1) / num_leaves) << " items per leaf."); - - Iterator it = ibegin; - for (size_t i = 0; i < num_leaves; ++i) { - // allocate new leaf node - LeafNode *leaf = allocate_leaf(); - - // copy keys or (key,value) pairs into leaf nodes, uses template - // switch leaf->set_slot(). - leaf->slotuse = static_cast(num_items / (num_leaves - i)); - for (size_t s = 0; s < leaf->slotuse; ++s, ++it) leaf->set_slot(s, *it); - - if (tail_leaf_ != nullptr) { - tail_leaf_->next_leaf = leaf; - leaf->prev_leaf = tail_leaf_; - } else { - head_leaf_ = leaf; - } - tail_leaf_ = leaf; - - num_items -= leaf->slotuse; - } - - TLX_BTREE_ASSERT(it == iend && num_items == 0); - - // if the btree is so small to fit into one leaf, then we're done. - if (head_leaf_ == tail_leaf_) { - root_ = head_leaf_; - return; - } - - TLX_BTREE_ASSERT(stats_.leaves == num_leaves); - - // create first level of inner nodes, pointing to the leaves. - size_t num_parents = (num_leaves + (inner_slotmax + 1) - 1) / (inner_slotmax + 1); - - TLX_BTREE_PRINT("BTree::bulk_load, level 1: " - << num_leaves << " leaves in " << num_parents << " inner nodes with up to " - << ((num_leaves + num_parents - 1) / num_parents) << " leaves per inner node."); - - // save inner nodes and maxkey for next level. - typedef std::pair nextlevel_type; - nextlevel_type *nextlevel = new nextlevel_type[num_parents]; - - LeafNode *leaf = head_leaf_; - for (size_t i = 0; i < num_parents; ++i) { - // allocate new inner node at level 1 - InnerNode *n = allocate_inner(1); - - n->slotuse = static_cast(num_leaves / (num_parents - i)); - TLX_BTREE_ASSERT(n->slotuse > 0); - // this counts keys, but an inner node has keys+1 children. - --n->slotuse; - - // copy last key from each leaf and set child - for (unsigned short s = 0; s < n->slotuse; ++s) { - n->slotkey[s] = leaf->key(leaf->slotuse - 1); - n->childid[s] = leaf; - leaf = leaf->next_leaf; - } - n->childid[n->slotuse] = leaf; - - // track max key of any descendant. - nextlevel[i].first = n; - nextlevel[i].second = &leaf->key(leaf->slotuse - 1); - - leaf = leaf->next_leaf; - num_leaves -= n->slotuse + 1; - } - - TLX_BTREE_ASSERT(leaf == nullptr && num_leaves == 0); - - // recursively build inner nodes pointing to inner nodes. - for (int level = 2; num_parents != 1; ++level) { - size_t num_children = num_parents; - num_parents = (num_children + (inner_slotmax + 1) - 1) / (inner_slotmax + 1); - - TLX_BTREE_PRINT("BTree::bulk_load, level " - << level << ": " << num_children << " children in " << num_parents << " inner nodes with up to " - << ((num_children + num_parents - 1) / num_parents) << " children per inner node."); - - size_t inner_index = 0; - for (size_t i = 0; i < num_parents; ++i) { - // allocate new inner node at level - InnerNode *n = allocate_inner(level); - - n->slotuse = static_cast(num_children / (num_parents - i)); - TLX_BTREE_ASSERT(n->slotuse > 0); - // this counts keys, but an inner node has keys+1 children. - --n->slotuse; - - // copy children and maxkeys from nextlevel - for (unsigned short s = 0; s < n->slotuse; ++s) { - n->slotkey[s] = *nextlevel[inner_index].second; - n->childid[s] = nextlevel[inner_index].first; - ++inner_index; - } - n->childid[n->slotuse] = nextlevel[inner_index].first; - - // reuse nextlevel array for parents, because we can overwrite - // slots we've already consumed. - nextlevel[i].first = n; - nextlevel[i].second = nextlevel[inner_index].second; - - ++inner_index; - num_children -= n->slotuse + 1; - } - - TLX_BTREE_ASSERT(num_children == 0); - } - - root_ = nextlevel[0].first; - delete[] nextlevel; - - if (self_verify) verify(); - } - - //! \} - - private: - //! \name Support Class Encapsulating Deletion Results - //! \{ - - //! Result flags of recursive deletion. - enum result_flags_t { - //! Deletion successful and no fix-ups necessary. - btree_ok = 0, - - //! Deletion not successful because key was not found. - btree_not_found = 1, - - //! Deletion successful, the last key was updated so parent slotkeys - //! need updates. - btree_update_lastkey = 2, - - //! Deletion successful, children nodes were merged and the parent needs - //! to remove the empty node. - btree_fixmerge = 4 - }; - - //! B+ tree recursive deletion has much information which is needs to be - //! passed upward. - struct result_t { - //! Merged result flags - result_flags_t flags; - - //! The key to be updated at the parent's slot - key_type lastkey; - - //! Constructor of a result with a specific flag, this can also be used - //! as for implicit conversion. - result_t(result_flags_t f = btree_ok) // NOLINT - : flags(f), lastkey() {} - - //! Constructor with a lastkey value. - result_t(result_flags_t f, const key_type &k) : flags(f), lastkey(k) {} - - //! Test if this result object has a given flag set. - bool has(result_flags_t f) const { return (flags & f) != 0; } - - //! Merge two results OR-ing the result flags and overwriting lastkeys. - result_t &operator|=(const result_t &other) { - flags = result_flags_t(flags | other.flags); - - // we overwrite existing lastkeys on purpose - if (other.has(btree_update_lastkey)) lastkey = other.lastkey; - - return *this; - } - }; - - //! \} - - public: - //! \name Public Erase Functions - //! \{ - - //! Erases one (the first) of the key/data pairs associated with the given - //! key. - bool erase_one(const key_type &key) { - TLX_BTREE_PRINT("BTree::erase_one(" << key << ") on btree size " << size()); - - if (self_verify) verify(); - - if (!root_) return false; - - result_t result = erase_one_descend(key, root_, nullptr, nullptr, nullptr, nullptr, nullptr, 0); - - if (!result.has(btree_not_found)) --stats_.size; - -#ifdef TLX_BTREE_DEBUG - if (debug) print(std::cout); -#endif - if (self_verify) verify(); - - return !result.has(btree_not_found); - } - - //! Erases all the key/data pairs associated with the given key. This is - //! implemented using erase_one(). - size_type erase(const key_type &key) { - size_type c = 0; - - while (erase_one(key)) { - ++c; - if (!allow_duplicates) break; - } - - return c; - } - - //! Erase the key/data pair referenced by the iterator. - void erase(iterator iter) { - TLX_BTREE_PRINT("BTree::erase_iter(" << iter.curr_leaf << "," << iter.curr_slot << ") on btree size " << size()); - - if (self_verify) verify(); - - if (!root_) return; - - result_t result = erase_iter_descend(iter, root_, nullptr, nullptr, nullptr, nullptr, nullptr, 0); - - if (!result.has(btree_not_found)) --stats_.size; - -#ifdef TLX_BTREE_DEBUG - if (debug) print(std::cout); -#endif - if (self_verify) verify(); - } - -#ifdef BTREE_TODO - //! Erase all key/data pairs in the range [first,last). This function is - //! currently not implemented by the B+ Tree. - void erase(iterator /* first */, iterator /* last */) { abort(); } -#endif - - //! \} - - private: - //! \name Private Erase Functions - //! \{ - - /*! - * Erase one (the first) key/data pair in the B+ tree matching key. - * - * Descends down the tree in search of key. During the descent the parent, - * left and right siblings and their parents are computed and passed - * down. Once the key/data pair is found, it is removed from the leaf. If - * the leaf underflows 6 different cases are handled. These cases resolve - * the underflow by shifting key/data pairs from adjacent sibling nodes, - * merging two sibling nodes or trimming the tree. - */ - result_t erase_one_descend(const key_type &key, node *curr, node *left, node *right, InnerNode *left_parent, - InnerNode *right_parent, InnerNode *parent, unsigned int parentslot) { - if (curr->is_leafnode()) { - LeafNode *leaf = static_cast(curr); - LeafNode *left_leaf = static_cast(left); - LeafNode *right_leaf = static_cast(right); - - unsigned short slot = find_lower(leaf, key); - - if (slot >= leaf->slotuse || !key_equal(key, leaf->key(slot))) { - TLX_BTREE_PRINT("Could not find key " << key << " to erase."); - - return btree_not_found; - } - - TLX_BTREE_PRINT("Found key in leaf " << curr << " at slot " << slot); - - std::copy(leaf->slotdata + slot + 1, leaf->slotdata + leaf->slotuse, leaf->slotdata + slot); - - leaf->slotuse--; - - result_t myres = btree_ok; - - // if the last key of the leaf was changed, the parent is notified - // and updates the key of this leaf - if (slot == leaf->slotuse) { - if (parent && parentslot < parent->slotuse) { - TLX_BTREE_ASSERT(parent->childid[parentslot] == curr); - parent->slotkey[parentslot] = leaf->key(leaf->slotuse - 1); - } else { - if (leaf->slotuse >= 1) { - TLX_BTREE_PRINT("Scheduling lastkeyupdate: key " << leaf->key(leaf->slotuse - 1)); - myres |= result_t(btree_update_lastkey, leaf->key(leaf->slotuse - 1)); - } else { - TLX_BTREE_ASSERT(leaf == root_); - } - } - } - - if (leaf->is_underflow() && !(leaf == root_ && leaf->slotuse >= 1)) { - // determine what to do about the underflow - - // case : if this empty leaf is the root, then delete all nodes - // and set root to nullptr. - if (left_leaf == nullptr && right_leaf == nullptr) { - TLX_BTREE_ASSERT(leaf == root_); - TLX_BTREE_ASSERT(leaf->slotuse == 0); - - free_node(root_); - - root_ = leaf = nullptr; - head_leaf_ = tail_leaf_ = nullptr; - - // will be decremented soon by insert_start() - TLX_BTREE_ASSERT(stats_.size == 1); - TLX_BTREE_ASSERT(stats_.leaves == 0); - TLX_BTREE_ASSERT(stats_.inner_nodes == 0); - - return btree_ok; - } - // case : if both left and right leaves would underflow in case - // of a shift, then merging is necessary. choose the more local - // merger with our parent - else if ((left_leaf == nullptr || left_leaf->is_few()) && (right_leaf == nullptr || right_leaf->is_few())) { - if (left_parent == parent) - myres |= merge_leaves(left_leaf, leaf, left_parent); - else - myres |= merge_leaves(leaf, right_leaf, right_parent); - } - // case : the right leaf has extra data, so balance right with - // current - else if ((left_leaf != nullptr && left_leaf->is_few()) && (right_leaf != nullptr && !right_leaf->is_few())) { - if (right_parent == parent) - myres |= shift_left_leaf(leaf, right_leaf, right_parent, parentslot); - else - myres |= merge_leaves(left_leaf, leaf, left_parent); - } - // case : the left leaf has extra data, so balance left with - // current - else if ((left_leaf != nullptr && !left_leaf->is_few()) && (right_leaf != nullptr && right_leaf->is_few())) { - if (left_parent == parent) - shift_right_leaf(left_leaf, leaf, left_parent, parentslot - 1); - else - myres |= merge_leaves(leaf, right_leaf, right_parent); - } - // case : both the leaf and right leaves have extra data and our - // parent, choose the leaf with more data - else if (left_parent == right_parent) { - if (left_leaf->slotuse <= right_leaf->slotuse) - myres |= shift_left_leaf(leaf, right_leaf, right_parent, parentslot); - else - shift_right_leaf(left_leaf, leaf, left_parent, parentslot - 1); - } else { - if (left_parent == parent) - shift_right_leaf(left_leaf, leaf, left_parent, parentslot - 1); - else - myres |= shift_left_leaf(leaf, right_leaf, right_parent, parentslot); - } - } - - return myres; - } else // !curr->is_leafnode() - { - InnerNode *inner = static_cast(curr); - InnerNode *left_inner = static_cast(left); - InnerNode *right_inner = static_cast(right); - - node *myleft, *myright; - InnerNode *myleft_parent, *myright_parent; - - unsigned short slot = find_lower(inner, key); - - if (slot == 0) { - myleft = (left == nullptr) ? nullptr : static_cast(left)->childid[left->slotuse - 1]; - myleft_parent = left_parent; - } else { - myleft = inner->childid[slot - 1]; - myleft_parent = inner; - } - - if (slot == inner->slotuse) { - myright = (right == nullptr) ? nullptr : static_cast(right)->childid[0]; - myright_parent = right_parent; - } else { - myright = inner->childid[slot + 1]; - myright_parent = inner; - } - - TLX_BTREE_PRINT("erase_one_descend into " << inner->childid[slot]); - - result_t result = - erase_one_descend(key, inner->childid[slot], myleft, myright, myleft_parent, myright_parent, inner, slot); - - result_t myres = btree_ok; - - if (result.has(btree_not_found)) { - return result; - } - - if (result.has(btree_update_lastkey)) { - if (parent && parentslot < parent->slotuse) { - TLX_BTREE_PRINT("Fixing lastkeyupdate: key " << result.lastkey << " into parent " << parent - << " at parentslot " << parentslot); - - TLX_BTREE_ASSERT(parent->childid[parentslot] == curr); - parent->slotkey[parentslot] = result.lastkey; - } else { - TLX_BTREE_PRINT("Forwarding lastkeyupdate: key " << result.lastkey); - myres |= result_t(btree_update_lastkey, result.lastkey); - } - } - - if (result.has(btree_fixmerge)) { - // either the current node or the next is empty and should be - // removed - if (inner->childid[slot]->slotuse != 0) slot++; - - // this is the child slot invalidated by the merge - TLX_BTREE_ASSERT(inner->childid[slot]->slotuse == 0); - - free_node(inner->childid[slot]); - - std::copy(inner->slotkey + slot, inner->slotkey + inner->slotuse, inner->slotkey + slot - 1); - std::copy(inner->childid + slot + 1, inner->childid + inner->slotuse + 1, inner->childid + slot); - - inner->slotuse--; - - if (inner->level == 1) { - // fix split key for children leaves - slot--; - LeafNode *child = static_cast(inner->childid[slot]); - inner->slotkey[slot] = child->key(child->slotuse - 1); - } - } - - if (inner->is_underflow() && !(inner == root_ && inner->slotuse >= 1)) { - // case: the inner node is the root and has just one child. that - // child becomes the new root - if (left_inner == nullptr && right_inner == nullptr) { - TLX_BTREE_ASSERT(inner == root_); - TLX_BTREE_ASSERT(inner->slotuse == 0); - - root_ = inner->childid[0]; - - inner->slotuse = 0; - free_node(inner); - - return btree_ok; - } - // case : if both left and right leaves would underflow in case - // of a shift, then merging is necessary. choose the more local - // merger with our parent - else if ((left_inner == nullptr || left_inner->is_few()) && (right_inner == nullptr || right_inner->is_few())) { - if (left_parent == parent) - myres |= merge_inner(left_inner, inner, left_parent, parentslot - 1); - else - myres |= merge_inner(inner, right_inner, right_parent, parentslot); - } - // case : the right leaf has extra data, so balance right with - // current - else if ((left_inner != nullptr && left_inner->is_few()) && - (right_inner != nullptr && !right_inner->is_few())) { - if (right_parent == parent) - shift_left_inner(inner, right_inner, right_parent, parentslot); - else - myres |= merge_inner(left_inner, inner, left_parent, parentslot - 1); - } - // case : the left leaf has extra data, so balance left with - // current - else if ((left_inner != nullptr && !left_inner->is_few()) && - (right_inner != nullptr && right_inner->is_few())) { - if (left_parent == parent) - shift_right_inner(left_inner, inner, left_parent, parentslot - 1); - else - myres |= merge_inner(inner, right_inner, right_parent, parentslot); - } - // case : both the leaf and right leaves have extra data and our - // parent, choose the leaf with more data - else if (left_parent == right_parent) { - if (left_inner->slotuse <= right_inner->slotuse) - shift_left_inner(inner, right_inner, right_parent, parentslot); - else - shift_right_inner(left_inner, inner, left_parent, parentslot - 1); - } else { - if (left_parent == parent) - shift_right_inner(left_inner, inner, left_parent, parentslot - 1); - else - shift_left_inner(inner, right_inner, right_parent, parentslot); - } - } - - return myres; - } - } - - /*! - * Erase one key/data pair referenced by an iterator in the B+ tree. - * - * Descends down the tree in search of an iterator. During the descent the - * parent, left and right siblings and their parents are computed and passed - * down. The difficulty is that the iterator contains only a pointer to a - * LeafNode, which means that this function must do a recursive depth first - * search for that leaf node in the subtree containing all pairs of the same - * key. This subtree can be very large, even the whole tree, though in - * practice it would not make sense to have so many duplicate keys. - * - * Once the referenced key/data pair is found, it is removed from the leaf - * and the same underflow cases are handled as in erase_one_descend. - */ - result_t erase_iter_descend(const iterator &iter, node *curr, node *left, node *right, InnerNode *left_parent, - InnerNode *right_parent, InnerNode *parent, unsigned int parentslot) { - if (curr->is_leafnode()) { - LeafNode *leaf = static_cast(curr); - LeafNode *left_leaf = static_cast(left); - LeafNode *right_leaf = static_cast(right); - - // if this is not the correct leaf, get next step in recursive - // search - if (leaf != iter.curr_leaf) { - return btree_not_found; - } - - if (iter.curr_slot >= leaf->slotuse) { - TLX_BTREE_PRINT("Could not find iterator (" << iter.curr_leaf << "," << iter.curr_slot - << ") to erase. Invalid leaf node?"); - - return btree_not_found; - } - - unsigned short slot = iter.curr_slot; - - TLX_BTREE_PRINT("Found iterator in leaf " << curr << " at slot " << slot); - - std::copy(leaf->slotdata + slot + 1, leaf->slotdata + leaf->slotuse, leaf->slotdata + slot); - - leaf->slotuse--; - - result_t myres = btree_ok; - - // if the last key of the leaf was changed, the parent is notified - // and updates the key of this leaf - if (slot == leaf->slotuse) { - if (parent && parentslot < parent->slotuse) { - TLX_BTREE_ASSERT(parent->childid[parentslot] == curr); - parent->slotkey[parentslot] = leaf->key(leaf->slotuse - 1); - } else { - if (leaf->slotuse >= 1) { - TLX_BTREE_PRINT("Scheduling lastkeyupdate: key " << leaf->key(leaf->slotuse - 1)); - myres |= result_t(btree_update_lastkey, leaf->key(leaf->slotuse - 1)); - } else { - TLX_BTREE_ASSERT(leaf == root_); - } - } - } - - if (leaf->is_underflow() && !(leaf == root_ && leaf->slotuse >= 1)) { - // determine what to do about the underflow - - // case : if this empty leaf is the root, then delete all nodes - // and set root to nullptr. - if (left_leaf == nullptr && right_leaf == nullptr) { - TLX_BTREE_ASSERT(leaf == root_); - TLX_BTREE_ASSERT(leaf->slotuse == 0); - - free_node(root_); - - root_ = leaf = nullptr; - head_leaf_ = tail_leaf_ = nullptr; - - // will be decremented soon by insert_start() - TLX_BTREE_ASSERT(stats_.size == 1); - TLX_BTREE_ASSERT(stats_.leaves == 0); - TLX_BTREE_ASSERT(stats_.inner_nodes == 0); - - return btree_ok; - } - // case : if both left and right leaves would underflow in case - // of a shift, then merging is necessary. choose the more local - // merger with our parent - else if ((left_leaf == nullptr || left_leaf->is_few()) && (right_leaf == nullptr || right_leaf->is_few())) { - if (left_parent == parent) - myres |= merge_leaves(left_leaf, leaf, left_parent); - else - myres |= merge_leaves(leaf, right_leaf, right_parent); - } - // case : the right leaf has extra data, so balance right with - // current - else if ((left_leaf != nullptr && left_leaf->is_few()) && (right_leaf != nullptr && !right_leaf->is_few())) { - if (right_parent == parent) { - myres |= shift_left_leaf(leaf, right_leaf, right_parent, parentslot); - } else { - myres |= merge_leaves(left_leaf, leaf, left_parent); - } - } - // case : the left leaf has extra data, so balance left with - // current - else if ((left_leaf != nullptr && !left_leaf->is_few()) && (right_leaf != nullptr && right_leaf->is_few())) { - if (left_parent == parent) { - shift_right_leaf(left_leaf, leaf, left_parent, parentslot - 1); - } else { - myres |= merge_leaves(leaf, right_leaf, right_parent); - } - } - // case : both the leaf and right leaves have extra data and our - // parent, choose the leaf with more data - else if (left_parent == right_parent) { - if (left_leaf->slotuse <= right_leaf->slotuse) { - myres |= shift_left_leaf(leaf, right_leaf, right_parent, parentslot); - } else { - shift_right_leaf(left_leaf, leaf, left_parent, parentslot - 1); - } - } else { - if (left_parent == parent) { - shift_right_leaf(left_leaf, leaf, left_parent, parentslot - 1); - } else { - myres |= shift_left_leaf(leaf, right_leaf, right_parent, parentslot); - } - } - } - - return myres; - } else // !curr->is_leafnode() - { - InnerNode *inner = static_cast(curr); - InnerNode *left_inner = static_cast(left); - InnerNode *right_inner = static_cast(right); - - // find first slot below which the searched iterator might be - // located. - - result_t result; - unsigned short slot = find_lower(inner, iter.key()); - - while (slot <= inner->slotuse) { - node *myleft, *myright; - InnerNode *myleft_parent, *myright_parent; - - if (slot == 0) { - myleft = (left == nullptr) ? nullptr : static_cast(left)->childid[left->slotuse - 1]; - myleft_parent = left_parent; - } else { - myleft = inner->childid[slot - 1]; - myleft_parent = inner; - } - - if (slot == inner->slotuse) { - myright = (right == nullptr) ? nullptr : static_cast(right)->childid[0]; - myright_parent = right_parent; - } else { - myright = inner->childid[slot + 1]; - myright_parent = inner; - } - - TLX_BTREE_PRINT("erase_iter_descend into " << inner->childid[slot]); - - result = - erase_iter_descend(iter, inner->childid[slot], myleft, myright, myleft_parent, myright_parent, inner, slot); - - if (!result.has(btree_not_found)) break; - - // continue recursive search for leaf on next slot - - if (slot < inner->slotuse && key_less(inner->slotkey[slot], iter.key())) return btree_not_found; - - ++slot; - } - - if (slot > inner->slotuse) return btree_not_found; - - result_t myres = btree_ok; - - if (result.has(btree_update_lastkey)) { - if (parent && parentslot < parent->slotuse) { - TLX_BTREE_PRINT("Fixing lastkeyupdate: key " << result.lastkey << " into parent " << parent - << " at parentslot " << parentslot); - - TLX_BTREE_ASSERT(parent->childid[parentslot] == curr); - parent->slotkey[parentslot] = result.lastkey; - } else { - TLX_BTREE_PRINT("Forwarding lastkeyupdate: key " << result.lastkey); - myres |= result_t(btree_update_lastkey, result.lastkey); - } - } - - if (result.has(btree_fixmerge)) { - // either the current node or the next is empty and should be - // removed - if (inner->childid[slot]->slotuse != 0) slot++; - - // this is the child slot invalidated by the merge - TLX_BTREE_ASSERT(inner->childid[slot]->slotuse == 0); - - free_node(inner->childid[slot]); - - std::copy(inner->slotkey + slot, inner->slotkey + inner->slotuse, inner->slotkey + slot - 1); - std::copy(inner->childid + slot + 1, inner->childid + inner->slotuse + 1, inner->childid + slot); - - inner->slotuse--; - - if (inner->level == 1) { - // fix split key for children leaves - slot--; - LeafNode *child = static_cast(inner->childid[slot]); - inner->slotkey[slot] = child->key(child->slotuse - 1); - } - } - - if (inner->is_underflow() && !(inner == root_ && inner->slotuse >= 1)) { - // case: the inner node is the root and has just one - // child. that child becomes the new root - if (left_inner == nullptr && right_inner == nullptr) { - TLX_BTREE_ASSERT(inner == root_); - TLX_BTREE_ASSERT(inner->slotuse == 0); - - root_ = inner->childid[0]; - - inner->slotuse = 0; - free_node(inner); - - return btree_ok; - } - // case : if both left and right leaves would underflow in case - // of a shift, then merging is necessary. choose the more local - // merger with our parent - else if ((left_inner == nullptr || left_inner->is_few()) && (right_inner == nullptr || right_inner->is_few())) { - if (left_parent == parent) { - myres |= merge_inner(left_inner, inner, left_parent, parentslot - 1); - } else { - myres |= merge_inner(inner, right_inner, right_parent, parentslot); - } - } - // case : the right leaf has extra data, so balance right with - // current - else if ((left_inner != nullptr && left_inner->is_few()) && - (right_inner != nullptr && !right_inner->is_few())) { - if (right_parent == parent) { - shift_left_inner(inner, right_inner, right_parent, parentslot); - } else { - myres |= merge_inner(left_inner, inner, left_parent, parentslot - 1); - } - } - // case : the left leaf has extra data, so balance left with - // current - else if ((left_inner != nullptr && !left_inner->is_few()) && - (right_inner != nullptr && right_inner->is_few())) { - if (left_parent == parent) { - shift_right_inner(left_inner, inner, left_parent, parentslot - 1); - } else { - myres |= merge_inner(inner, right_inner, right_parent, parentslot); - } - } - // case : both the leaf and right leaves have extra data and our - // parent, choose the leaf with more data - else if (left_parent == right_parent) { - if (left_inner->slotuse <= right_inner->slotuse) { - shift_left_inner(inner, right_inner, right_parent, parentslot); - } else { - shift_right_inner(left_inner, inner, left_parent, parentslot - 1); - } - } else { - if (left_parent == parent) { - shift_right_inner(left_inner, inner, left_parent, parentslot - 1); - } else { - shift_left_inner(inner, right_inner, right_parent, parentslot); - } - } - } - - return myres; - } - } - - //! Merge two leaf nodes. The function moves all key/data pairs from right - //! to left and sets right's slotuse to zero. The right slot is then removed - //! by the calling parent node. - result_t merge_leaves(LeafNode *left, LeafNode *right, InnerNode *parent) { - TLX_BTREE_PRINT("Merge leaf nodes " << left << " and " << right << " with common parent " << parent << "."); - (void)parent; - - TLX_BTREE_ASSERT(left->is_leafnode() && right->is_leafnode()); - TLX_BTREE_ASSERT(parent->level == 1); - - TLX_BTREE_ASSERT(left->slotuse + right->slotuse < leaf_slotmax); - - std::copy(right->slotdata, right->slotdata + right->slotuse, left->slotdata + left->slotuse); - - left->slotuse += right->slotuse; - - left->next_leaf = right->next_leaf; - if (left->next_leaf) - left->next_leaf->prev_leaf = left; - else - tail_leaf_ = left; - - right->slotuse = 0; - - return btree_fixmerge; - } - - //! Merge two inner nodes. The function moves all key/childid pairs from - //! right to left and sets right's slotuse to zero. The right slot is then - //! removed by the calling parent node. - static result_t merge_inner(InnerNode *left, InnerNode *right, InnerNode *parent, unsigned int parentslot) { - TLX_BTREE_PRINT("Merge inner nodes " << left << " and " << right << " with common parent " << parent << "."); - - TLX_BTREE_ASSERT(left->level == right->level); - TLX_BTREE_ASSERT(parent->level == left->level + 1); - - TLX_BTREE_ASSERT(parent->childid[parentslot] == left); - - TLX_BTREE_ASSERT(left->slotuse + right->slotuse < inner_slotmax); - - if (self_verify) { - // find the left node's slot in the parent's children - unsigned int leftslot = 0; - while (leftslot <= parent->slotuse && parent->childid[leftslot] != left) ++leftslot; - - TLX_BTREE_ASSERT(leftslot < parent->slotuse); - TLX_BTREE_ASSERT(parent->childid[leftslot] == left); - TLX_BTREE_ASSERT(parent->childid[leftslot + 1] == right); - - TLX_BTREE_ASSERT(parentslot == leftslot); - } - - // retrieve the decision key from parent - left->slotkey[left->slotuse] = parent->slotkey[parentslot]; - left->slotuse++; - - // copy over keys and children from right - std::copy(right->slotkey, right->slotkey + right->slotuse, left->slotkey + left->slotuse); - std::copy(right->childid, right->childid + right->slotuse + 1, left->childid + left->slotuse); - - left->slotuse += right->slotuse; - right->slotuse = 0; - - return btree_fixmerge; - } - - //! Balance two leaf nodes. The function moves key/data pairs from right to - //! left so that both nodes are equally filled. The parent node is updated - //! if possible. - static result_t shift_left_leaf(LeafNode *left, LeafNode *right, InnerNode *parent, unsigned int parentslot) { - TLX_BTREE_ASSERT(left->is_leafnode() && right->is_leafnode()); - TLX_BTREE_ASSERT(parent->level == 1); - - TLX_BTREE_ASSERT(left->next_leaf == right); - TLX_BTREE_ASSERT(left == right->prev_leaf); - - TLX_BTREE_ASSERT(left->slotuse < right->slotuse); - TLX_BTREE_ASSERT(parent->childid[parentslot] == left); - - unsigned int shiftnum = (right->slotuse - left->slotuse) >> 1; - - TLX_BTREE_PRINT("Shifting (leaf) " << shiftnum << " entries to left " << left << " from right " << right - << " with common parent " << parent << "."); - - TLX_BTREE_ASSERT(left->slotuse + shiftnum < leaf_slotmax); - - // copy the first items from the right node to the last slot in the left - // node. - - std::copy(right->slotdata, right->slotdata + shiftnum, left->slotdata + left->slotuse); - - left->slotuse += shiftnum; - - // shift all slots in the right node to the left - - std::copy(right->slotdata + shiftnum, right->slotdata + right->slotuse, right->slotdata); - - right->slotuse -= shiftnum; - - // fixup parent - if (parentslot < parent->slotuse) { - parent->slotkey[parentslot] = left->key(left->slotuse - 1); - return btree_ok; - } else { // the update is further up the tree - return result_t(btree_update_lastkey, left->key(left->slotuse - 1)); - } - } - - //! Balance two inner nodes. The function moves key/data pairs from right to - //! left so that both nodes are equally filled. The parent node is updated - //! if possible. - static void shift_left_inner(InnerNode *left, InnerNode *right, InnerNode *parent, unsigned int parentslot) { - TLX_BTREE_ASSERT(left->level == right->level); - TLX_BTREE_ASSERT(parent->level == left->level + 1); - - TLX_BTREE_ASSERT(left->slotuse < right->slotuse); - TLX_BTREE_ASSERT(parent->childid[parentslot] == left); - - unsigned int shiftnum = (right->slotuse - left->slotuse) >> 1; - - TLX_BTREE_PRINT("Shifting (inner) " << shiftnum << " entries to left " << left << " from right " << right - << " with common parent " << parent << "."); - - TLX_BTREE_ASSERT(left->slotuse + shiftnum < inner_slotmax); - - if (self_verify) { - // find the left node's slot in the parent's children and compare to - // parentslot - - unsigned int leftslot = 0; - while (leftslot <= parent->slotuse && parent->childid[leftslot] != left) ++leftslot; - - TLX_BTREE_ASSERT(leftslot < parent->slotuse); - TLX_BTREE_ASSERT(parent->childid[leftslot] == left); - TLX_BTREE_ASSERT(parent->childid[leftslot + 1] == right); - - TLX_BTREE_ASSERT(leftslot == parentslot); - } - - // copy the parent's decision slotkey and childid to the first new key - // on the left - left->slotkey[left->slotuse] = parent->slotkey[parentslot]; - left->slotuse++; - - // copy the other items from the right node to the last slots in the - // left node. - std::copy(right->slotkey, right->slotkey + shiftnum - 1, left->slotkey + left->slotuse); - std::copy(right->childid, right->childid + shiftnum, left->childid + left->slotuse); - - left->slotuse += shiftnum - 1; - - // fixup parent - parent->slotkey[parentslot] = right->slotkey[shiftnum - 1]; - - // shift all slots in the right node - std::copy(right->slotkey + shiftnum, right->slotkey + right->slotuse, right->slotkey); - std::copy(right->childid + shiftnum, right->childid + right->slotuse + 1, right->childid); - - right->slotuse -= shiftnum; - } - - //! Balance two leaf nodes. The function moves key/data pairs from left to - //! right so that both nodes are equally filled. The parent node is updated - //! if possible. - static void shift_right_leaf(LeafNode *left, LeafNode *right, InnerNode *parent, unsigned int parentslot) { - TLX_BTREE_ASSERT(left->is_leafnode() && right->is_leafnode()); - TLX_BTREE_ASSERT(parent->level == 1); - - TLX_BTREE_ASSERT(left->next_leaf == right); - TLX_BTREE_ASSERT(left == right->prev_leaf); - TLX_BTREE_ASSERT(parent->childid[parentslot] == left); - - TLX_BTREE_ASSERT(left->slotuse > right->slotuse); - - unsigned int shiftnum = (left->slotuse - right->slotuse) >> 1; - - TLX_BTREE_PRINT("Shifting (leaf) " << shiftnum << " entries to right " << right << " from left " << left - << " with common parent " << parent << "."); - - if (self_verify) { - // find the left node's slot in the parent's children - unsigned int leftslot = 0; - while (leftslot <= parent->slotuse && parent->childid[leftslot] != left) ++leftslot; - - TLX_BTREE_ASSERT(leftslot < parent->slotuse); - TLX_BTREE_ASSERT(parent->childid[leftslot] == left); - TLX_BTREE_ASSERT(parent->childid[leftslot + 1] == right); - - TLX_BTREE_ASSERT(leftslot == parentslot); - } - - // shift all slots in the right node - - TLX_BTREE_ASSERT(right->slotuse + shiftnum < leaf_slotmax); - - std::copy_backward(right->slotdata, right->slotdata + right->slotuse, right->slotdata + right->slotuse + shiftnum); - - right->slotuse += shiftnum; - - // copy the last items from the left node to the first slot in the right - // node. - std::copy(left->slotdata + left->slotuse - shiftnum, left->slotdata + left->slotuse, right->slotdata); - - left->slotuse -= shiftnum; - - parent->slotkey[parentslot] = left->key(left->slotuse - 1); - } - - //! Balance two inner nodes. The function moves key/data pairs from left to - //! right so that both nodes are equally filled. The parent node is updated - //! if possible. - static void shift_right_inner(InnerNode *left, InnerNode *right, InnerNode *parent, unsigned int parentslot) { - TLX_BTREE_ASSERT(left->level == right->level); - TLX_BTREE_ASSERT(parent->level == left->level + 1); - - TLX_BTREE_ASSERT(left->slotuse > right->slotuse); - TLX_BTREE_ASSERT(parent->childid[parentslot] == left); - - unsigned int shiftnum = (left->slotuse - right->slotuse) >> 1; - - TLX_BTREE_PRINT("Shifting (leaf) " << shiftnum << " entries to right " << right << " from left " << left - << " with common parent " << parent << "."); - - if (self_verify) { - // find the left node's slot in the parent's children - unsigned int leftslot = 0; - while (leftslot <= parent->slotuse && parent->childid[leftslot] != left) ++leftslot; - - TLX_BTREE_ASSERT(leftslot < parent->slotuse); - TLX_BTREE_ASSERT(parent->childid[leftslot] == left); - TLX_BTREE_ASSERT(parent->childid[leftslot + 1] == right); - - TLX_BTREE_ASSERT(leftslot == parentslot); - } - - // shift all slots in the right node - - TLX_BTREE_ASSERT(right->slotuse + shiftnum < inner_slotmax); - - std::copy_backward(right->slotkey, right->slotkey + right->slotuse, right->slotkey + right->slotuse + shiftnum); - std::copy_backward(right->childid, right->childid + right->slotuse + 1, - right->childid + right->slotuse + 1 + shiftnum); - - right->slotuse += shiftnum; - - // copy the parent's decision slotkey and childid to the last new key on - // the right - right->slotkey[shiftnum - 1] = parent->slotkey[parentslot]; - - // copy the remaining last items from the left node to the first slot in - // the right node. - std::copy(left->slotkey + left->slotuse - shiftnum + 1, left->slotkey + left->slotuse, right->slotkey); - std::copy(left->childid + left->slotuse - shiftnum + 1, left->childid + left->slotuse + 1, right->childid); - - // copy the first to-be-removed key from the left node to the parent's - // decision slot - parent->slotkey[parentslot] = left->slotkey[left->slotuse - shiftnum]; - - left->slotuse -= shiftnum; - } - - //! \} - -#ifdef TLX_BTREE_DEBUG - - public: - //! \name Debug Printing - //! \{ - - //! Print out the B+ tree structure with keys onto the given ostream. This - //! function requires that the header is compiled with TLX_BTREE_DEBUG and - //! that key_type is printable via std::ostream. - void print(std::ostream &os) const { - if (root_) { - print_node(os, root_, 0, true); - } - } - - //! Print out only the leaves via the double linked list. - void print_leaves(std::ostream &os) const { - os << "leaves:" << std::endl; - - const LeafNode *n = head_leaf_; - - while (n) { - os << " " << n << std::endl; - - n = n->next_leaf; - } - } - - private: - //! Recursively descend down the tree and print out nodes. - static void print_node(std::ostream &os, const node *node, unsigned int depth = 0, bool recursive = false) { - for (unsigned int i = 0; i < depth; i++) os << " "; - - os << "node " << node << " level " << node->level << " slotuse " << node->slotuse << std::endl; - - if (node->is_leafnode()) { - const LeafNode *leafnode = static_cast(node); - - for (unsigned int i = 0; i < depth; i++) os << " "; - os << " leaf prev " << leafnode->prev_leaf << " next " << leafnode->next_leaf << std::endl; - - for (unsigned int i = 0; i < depth; i++) os << " "; - - for (unsigned short slot = 0; slot < leafnode->slotuse; ++slot) { - // os << leafnode->key(slot) << " " - // << "(data: " << leafnode->slotdata[slot] << ") "; - os << leafnode->key(slot) << " "; - } - os << std::endl; - } else { - const InnerNode *innernode = static_cast(node); - - for (unsigned int i = 0; i < depth; i++) os << " "; - - for (unsigned short slot = 0; slot < innernode->slotuse; ++slot) { - os << "(" << innernode->childid[slot] << ") " << innernode->slotkey[slot] << " "; - } - os << "(" << innernode->childid[innernode->slotuse] << ")" << std::endl; - - if (recursive) { - for (unsigned short slot = 0; slot < innernode->slotuse + 1; ++slot) { - print_node(os, innernode->childid[slot], depth + 1, recursive); - } - } - } - } - - //! \} -#endif - - public: - //! \name Verification of B+ Tree Invariants - //! \{ - - //! Run a thorough verification of all B+ tree invariants. The program - //! aborts via tlx_die_unless() if something is wrong. - void verify() const { - key_type minkey, maxkey; - tree_stats vstats; - - if (root_) { - verify_node(root_, &minkey, &maxkey, vstats); - - tlx_die_unless(vstats.size == stats_.size); - tlx_die_unless(vstats.leaves == stats_.leaves); - tlx_die_unless(vstats.inner_nodes == stats_.inner_nodes); - - verify_leaflinks(); - } - } - - private: - //! Recursively descend down the tree and verify each node - void verify_node(const node *n, key_type *minkey, key_type *maxkey, tree_stats &vstats) const { - TLX_BTREE_PRINT("verifynode " << n); - - if (n->is_leafnode()) { - const LeafNode *leaf = static_cast(n); - - tlx_die_unless(leaf == root_ || !leaf->is_underflow()); - tlx_die_unless(leaf->slotuse > 0); - - for (unsigned short slot = 0; slot < leaf->slotuse - 1; ++slot) { - tlx_die_unless(key_lessequal(leaf->key(slot), leaf->key(slot + 1))); - } - - *minkey = leaf->key(0); - *maxkey = leaf->key(leaf->slotuse - 1); - - vstats.leaves++; - vstats.size += leaf->slotuse; - } else // !n->is_leafnode() - { - const InnerNode *inner = static_cast(n); - vstats.inner_nodes++; - - tlx_die_unless(inner == root_ || !inner->is_underflow()); - tlx_die_unless(inner->slotuse > 0); - - for (unsigned short slot = 0; slot < inner->slotuse - 1; ++slot) { - tlx_die_unless(key_lessequal(inner->key(slot), inner->key(slot + 1))); - } - - for (unsigned short slot = 0; slot <= inner->slotuse; ++slot) { - const node *subnode = inner->childid[slot]; - key_type subminkey = key_type(); - key_type submaxkey = key_type(); - - tlx_die_unless(subnode->level + 1 == inner->level); - verify_node(subnode, &subminkey, &submaxkey, vstats); - - TLX_BTREE_PRINT("verify subnode " << subnode << ": " << subminkey << " - " << submaxkey); - - if (slot == 0) - *minkey = subminkey; - else - tlx_die_unless(key_greaterequal(subminkey, inner->key(slot - 1))); - - if (slot == inner->slotuse) - *maxkey = submaxkey; - else - tlx_die_unless(key_equal(inner->key(slot), submaxkey)); - - if (inner->level == 1 && slot < inner->slotuse) { - // children are leaves and must be linked together in the - // correct order - const LeafNode *leafa = static_cast(inner->childid[slot]); - const LeafNode *leafb = static_cast(inner->childid[slot + 1]); - - tlx_die_unless(leafa->next_leaf == leafb); - tlx_die_unless(leafa == leafb->prev_leaf); - } - if (inner->level == 2 && slot < inner->slotuse) { - // verify leaf links between the adjacent inner nodes - const InnerNode *parenta = static_cast(inner->childid[slot]); - const InnerNode *parentb = static_cast(inner->childid[slot + 1]); - - const LeafNode *leafa = static_cast(parenta->childid[parenta->slotuse]); - const LeafNode *leafb = static_cast(parentb->childid[0]); - - tlx_die_unless(leafa->next_leaf == leafb); - tlx_die_unless(leafa == leafb->prev_leaf); - } - } - } - } - - //! Verify the double linked list of leaves. - void verify_leaflinks() const { - const LeafNode *n = head_leaf_; - - tlx_die_unless(n->level == 0); - tlx_die_unless(!n || n->prev_leaf == nullptr); - - unsigned int testcount = 0; - - while (n) { - tlx_die_unless(n->level == 0); - tlx_die_unless(n->slotuse > 0); - - for (unsigned short slot = 0; slot < n->slotuse - 1; ++slot) { - tlx_die_unless(key_lessequal(n->key(slot), n->key(slot + 1))); - } - - testcount += n->slotuse; - - if (n->next_leaf) { - tlx_die_unless(key_lessequal(n->key(n->slotuse - 1), n->next_leaf->key(0))); - - tlx_die_unless(n == n->next_leaf->prev_leaf); - } else { - tlx_die_unless(tail_leaf_ == n); - } - - n = n->next_leaf; - } - - tlx_die_unless(testcount == size()); - } - - //! \} -}; - -//! \} -//! \} - -} // namespace tlx - -#endif // !TLX_CONTAINER_BTREE_HEADER - -/******************************************************************************/ diff --git a/tests/benchmark/btree_map.hpp b/tests/benchmark/btree_map.hpp deleted file mode 100644 index 3f718677b..000000000 --- a/tests/benchmark/btree_map.hpp +++ /dev/null @@ -1,475 +0,0 @@ -/******************************************************************************* - * tlx/container/btree_map.hpp - * - * Part of tlx - http://panthema.net/tlx - * - * Copyright (C) 2008-2017 Timo Bingmann - * - * All rights reserved. Published under the Boost Software License, Version 1.0 - ******************************************************************************/ - -#ifndef TLX_CONTAINER_BTREE_MAP_HEADER -#define TLX_CONTAINER_BTREE_MAP_HEADER - -#include -#include -#include - -#include "btree.hpp" - -namespace tlx { - -//! \addtogroup tlx_container_btree -//! \{ - -/*! - * Specialized B+ tree template class implementing STL's map container. - * - * Implements the STL map using a B+ tree. It can be used as a drop-in - * replacement for std::map. Not all asymptotic time requirements are met in - * theory. The class has a traits class defining B+ tree properties like slots - * and self-verification. Furthermore an allocator can be specified for tree - * nodes. - */ -template , - typename Traits_ = btree_default_traits>, - typename Alloc_ = std::allocator>> -class btree_map { - public: - //! \name Template Parameter Types - //! \{ - - //! First template parameter: The key type of the btree. This is stored in - //! inner nodes. - typedef Key_ key_type; - - //! Second template parameter: The value type associated with each key. - //! Stored in the B+ tree's leaves - typedef Data_ data_type; - - //! Third template parameter: Key comparison function object - typedef Compare_ key_compare; - - //! Fourth template parameter: Traits object used to define more parameters - //! of the B+ tree - typedef Traits_ traits; - - //! Fifth template parameter: STL allocator - typedef Alloc_ allocator_type; - - //! \} - - // The macro TLX_BTREE_FRIENDS can be used by outside class to access the B+ - // tree internals. This was added for wxBTreeDemo to be able to draw the - // tree. - TLX_BTREE_FRIENDS; - - public: - //! \name Constructed Types - //! \{ - - //! Typedef of our own type - typedef btree_map self; - - //! Construct the STL-required value_type as a composition pair of key and - //! data types - typedef std::pair value_type; - - //! Key Extractor Struct - struct key_of_value { - //! pull first out of pair - static const key_type &get(const value_type &v) { return v.first; } - }; - - //! Implementation type of the btree_base - typedef BTree btree_impl; - - //! Function class comparing two value_type pairs. - typedef typename btree_impl::value_compare value_compare; - - //! Size type used to count keys - typedef typename btree_impl::size_type size_type; - - //! Small structure containing statistics about the tree - typedef typename btree_impl::tree_stats tree_stats; - - //! \} - - public: - //! \name Static Constant Options and Values of the B+ Tree - //! \{ - - //! Base B+ tree parameter: The number of key/data slots in each leaf - static const unsigned short leaf_slotmax = btree_impl::leaf_slotmax; - - //! Base B+ tree parameter: The number of key slots in each inner node, - //! this can differ from slots in each leaf. - static const unsigned short inner_slotmax = btree_impl::inner_slotmax; - - //! Computed B+ tree parameter: The minimum number of key/data slots used - //! in a leaf. If fewer slots are used, the leaf will be merged or slots - //! shifted from it's siblings. - static const unsigned short leaf_slotmin = btree_impl::leaf_slotmin; - - //! Computed B+ tree parameter: The minimum number of key slots used - //! in an inner node. If fewer slots are used, the inner node will be - //! merged or slots shifted from it's siblings. - static const unsigned short inner_slotmin = btree_impl::inner_slotmin; - - //! Debug parameter: Enables expensive and thorough checking of the B+ tree - //! invariants after each insert/erase operation. - static const bool self_verify = btree_impl::self_verify; - - //! Debug parameter: Prints out lots of debug information about how the - //! algorithms change the tree. Requires the header file to be compiled - //! with TLX_BTREE_DEBUG and the key type must be std::ostream printable. - static const bool debug = btree_impl::debug; - - //! Operational parameter: Allow duplicate keys in the btree. - static const bool allow_duplicates = btree_impl::allow_duplicates; - - //! \} - - public: - //! \name Iterators and Reverse Iterators - //! \{ - - //! STL-like iterator object for B+ tree items. The iterator points to a - //! specific slot number in a leaf. - typedef typename btree_impl::iterator iterator; - - //! STL-like iterator object for B+ tree items. The iterator points to a - //! specific slot number in a leaf. - typedef typename btree_impl::const_iterator const_iterator; - - //! create mutable reverse iterator by using STL magic - typedef typename btree_impl::reverse_iterator reverse_iterator; - - //! create constant reverse iterator by using STL magic - typedef typename btree_impl::const_reverse_iterator const_reverse_iterator; - - //! \} - - private: - //! \name Tree Implementation Object - //! \{ - - //! The contained implementation object - btree_impl tree_; - - //! \} - - public: - //! \name Constructors and Destructor - //! \{ - - //! Default constructor initializing an empty B+ tree with the standard key - //! comparison function - explicit btree_map(const allocator_type &alloc = allocator_type()) : tree_(alloc) {} - - //! Constructor initializing an empty B+ tree with a special key - //! comparison object - explicit btree_map(const key_compare &kcf, const allocator_type &alloc = allocator_type()) : tree_(kcf, alloc) {} - - //! Constructor initializing a B+ tree with the range [first,last) - template - btree_map(InputIterator first, InputIterator last, const allocator_type &alloc = allocator_type()) - : tree_(first, last, alloc) {} - - //! Constructor initializing a B+ tree with the range [first,last) and a - //! special key comparison object - template - btree_map(InputIterator first, InputIterator last, const key_compare &kcf, - const allocator_type &alloc = allocator_type()) - : tree_(first, last, kcf, alloc) {} - - //! Frees up all used B+ tree memory pages - ~btree_map() {} - - //! Fast swapping of two identical B+ tree objects. - void swap(btree_map &from) { std::swap(tree_, from.tree_); } - - //! \} - - public: - //! \name Key and Value Comparison Function Objects - //! \{ - - //! Constant access to the key comparison object sorting the B+ tree - key_compare key_comp() const { return tree_.key_comp(); } - - //! Constant access to a constructed value_type comparison object. required - //! by the STL - value_compare value_comp() const { return tree_.value_comp(); } - - //! \} - - public: - //! \name Allocators - //! \{ - - //! Return the base node allocator provided during construction. - allocator_type get_allocator() const { return tree_.get_allocator(); } - - //! \} - - public: - //! \name Fast Destruction of the B+ Tree - //! \{ - - //! Frees all key/data pairs and all nodes of the tree - void clear() { tree_.clear(); } - - //! \} - - public: - //! \name STL Iterator Construction Functions - //! \{ - - //! Constructs a read/data-write iterator that points to the first slot in - //! the first leaf of the B+ tree. - iterator begin() { return tree_.begin(); } - - //! Constructs a read/data-write iterator that points to the first invalid - //! slot in the last leaf of the B+ tree. - iterator end() { return tree_.end(); } - - //! Constructs a read-only constant iterator that points to the first slot - //! in the first leaf of the B+ tree. - const_iterator begin() const { return tree_.begin(); } - - //! Constructs a read-only constant iterator that points to the first - //! invalid slot in the last leaf of the B+ tree. - const_iterator end() const { return tree_.end(); } - - //! Constructs a read/data-write reverse iterator that points to the first - //! invalid slot in the last leaf of the B+ tree. Uses STL magic. - reverse_iterator rbegin() { return tree_.rbegin(); } - - //! Constructs a read/data-write reverse iterator that points to the first - //! slot in the first leaf of the B+ tree. Uses STL magic. - reverse_iterator rend() { return tree_.rend(); } - - //! Constructs a read-only reverse iterator that points to the first - //! invalid slot in the last leaf of the B+ tree. Uses STL magic. - const_reverse_iterator rbegin() const { return tree_.rbegin(); } - - //! Constructs a read-only reverse iterator that points to the first slot - //! in the first leaf of the B+ tree. Uses STL magic. - const_reverse_iterator rend() const { return tree_.rend(); } - - //! \} - - public: - //! \name Access Functions to the Item Count - //! \{ - - //! Return the number of key/data pairs in the B+ tree - size_type size() const { return tree_.size(); } - - //! Returns true if there is at least one key/data pair in the B+ tree - bool empty() const { return tree_.empty(); } - - //! Returns the largest possible size of the B+ Tree. This is just a - //! function required by the STL standard, the B+ Tree can hold more items. - size_type max_size() const { return tree_.max_size(); } - - //! Return a const reference to the current statistics. - const tree_stats &get_stats() const { return tree_.get_stats(); } - - //! \} - - public: - //! \name STL Access Functions Querying the Tree by Descending to a Leaf - //! \{ - - //! Non-STL function checking whether a key is in the B+ tree. The same as - //! (find(k) != end()) or (count() != 0). - bool exists(const key_type &key) const { return tree_.exists(key); } - - //! Tries to locate a key in the B+ tree and returns an iterator to the - //! key/data slot if found. If unsuccessful it returns end(). - iterator find(const key_type &key) { return tree_.find(key); } - - //! Tries to locate a key in the B+ tree and returns an constant iterator to - //! the key/data slot if found. If unsuccessful it returns end(). - const_iterator find(const key_type &key) const { return tree_.find(key); } - - //! Tries to locate a key in the B+ tree and returns the number of identical - //! key entries found. Since this is a unique map, count() returns either 0 - //! or 1. - size_type count(const key_type &key) const { return tree_.count(key); } - - //! Searches the B+ tree and returns an iterator to the first pair equal to - //! or greater than key, or end() if all keys are smaller. - iterator lower_bound(const key_type &key) { return tree_.lower_bound(key); } - - //! Searches the B+ tree and returns a constant iterator to the first pair - //! equal to or greater than key, or end() if all keys are smaller. - const_iterator lower_bound(const key_type &key) const { return tree_.lower_bound(key); } - - //! Searches the B+ tree and returns an iterator to the first pair greater - //! than key, or end() if all keys are smaller or equal. - iterator upper_bound(const key_type &key) { return tree_.upper_bound(key); } - - //! Searches the B+ tree and returns a constant iterator to the first pair - //! greater than key, or end() if all keys are smaller or equal. - const_iterator upper_bound(const key_type &key) const { return tree_.upper_bound(key); } - - //! Searches the B+ tree and returns both lower_bound() and upper_bound(). - std::pair equal_range(const key_type &key) { return tree_.equal_range(key); } - - //! Searches the B+ tree and returns both lower_bound() and upper_bound(). - std::pair equal_range(const key_type &key) const { return tree_.equal_range(key); } - - //! \} - - public: - //! \name B+ Tree Object Comparison Functions - //! \{ - - //! Equality relation of B+ trees of the same type. B+ trees of the same - //! size and equal elements (both key and data) are considered equal. - bool operator==(const btree_map &other) const { return (tree_ == other.tree_); } - - //! Inequality relation. Based on operator==. - bool operator!=(const btree_map &other) const { return (tree_ != other.tree_); } - - //! Total ordering relation of B+ trees of the same type. It uses - //! std::lexicographical_compare() for the actual comparison of elements. - bool operator<(const btree_map &other) const { return (tree_ < other.tree_); } - - //! Greater relation. Based on operator<. - bool operator>(const btree_map &other) const { return (tree_ > other.tree_); } - - //! Less-equal relation. Based on operator<. - bool operator<=(const btree_map &other) const { return (tree_ <= other.tree_); } - - //! Greater-equal relation. Based on operator<. - bool operator>=(const btree_map &other) const { return (tree_ >= other.tree_); } - - //! \} - - public: - //! \name Fast Copy: Assign Operator and Copy Constructors - //! \{ - - //! Assignment operator. All the key/data pairs are copied - btree_map &operator=(const btree_map &other) { - if (this != &other) tree_ = other.tree_; - return *this; - } - - //! Copy constructor. The newly initialized B+ tree object will contain a - //! copy of all key/data pairs. - btree_map(const btree_map &other) : tree_(other.tree_) {} - - //! \} - - public: - //! \name Public Insertion Functions - //! \{ - - //! Attempt to insert a key/data pair into the B+ tree. Fails if the pair is - //! already present. - std::pair insert(const value_type &x) { return tree_.insert(x); } - - //! Attempt to insert a key/data pair into the B+ tree. This function is the - //! same as the other insert. Fails if the inserted pair is already present. - std::pair insert2(const key_type &key, const data_type &data) { - return tree_.insert(value_type(key, data)); - } - - //! Attempt to insert a key/data pair into the B+ tree. The iterator hint is - //! currently ignored by the B+ tree insertion routine. - iterator insert(iterator hint, const value_type &x) { return tree_.insert(hint, x); } - - //! Attempt to insert a key/data pair into the B+ tree. The iterator hint is - //! currently ignored by the B+ tree insertion routine. - iterator insert2(iterator hint, const key_type &key, const data_type &data) { - return tree_.insert(hint, value_type(key, data)); - } - - //! Returns a reference to the object that is associated with a particular - //! key. If the map does not already contain such an object, operator[] - //! inserts the default object data_type(). - data_type &operator[](const key_type &key) { - iterator i = insert(value_type(key, data_type())).first; - return i->second; - } - - //! Attempt to insert the range [first,last) of value_type pairs into the B+ - //! tree. Each key/data pair is inserted individually. - template - void insert(InputIterator first, InputIterator last) { - return tree_.insert(first, last); - } - - //! Bulk load a sorted range [first,last). Loads items into leaves and - //! constructs a B-tree above them. The tree must be empty when calling this - //! function. - template - void bulk_load(Iterator first, Iterator last) { - return tree_.bulk_load(first, last); - } - - //! \} - - public: - //! \name Public Erase Functions - //! \{ - - //! Erases the key/data pairs associated with the given key. For this - //! unique-associative map there is no difference to erase(). - bool erase_one(const key_type &key) { return tree_.erase_one(key); } - - //! Erases all the key/data pairs associated with the given key. This is - //! implemented using erase_one(). - size_type erase(const key_type &key) { return tree_.erase(key); } - - //! Erase the key/data pair referenced by the iterator. - void erase(iterator iter) { return tree_.erase(iter); } - -#ifdef TLX_BTREE_TODO - //! Erase all key/data pairs in the range [first,last). This function is - //! currently not implemented by the B+ Tree. - void erase(iterator /* first */, iterator /* last */) { abort(); } -#endif - - //! \} - -#ifdef TLX_BTREE_DEBUG - - public: - //! \name Debug Printing - //! \{ - - //! Print out the B+ tree structure with keys onto the given ostream. This - //! function requires that the header is compiled with TLX_BTREE_DEBUG and - //! that key_type is printable via std::ostream. - void print(std::ostream &os) const { tree_.print(os); } - - //! Print out only the leaves via the double linked list. - void print_leaves(std::ostream &os) const { tree_.print_leaves(os); } - - //! \} -#endif - - public: - //! \name Verification of B+ Tree Invariants - //! \{ - - //! Run a thorough verification of all B+ tree invariants. The program - //! aborts via TLX_BTREE_ASSERT() if something is wrong. - void verify() const { tree_.verify(); } - - //! \} -}; - -//! \} - -} // namespace tlx - -#endif // !TLX_CONTAINER_BTREE_MAP_HEADER - -/******************************************************************************/ diff --git a/tests/benchmark/core.hpp b/tests/benchmark/core.hpp deleted file mode 100644 index 55e40bbed..000000000 --- a/tests/benchmark/core.hpp +++ /dev/null @@ -1,267 +0,0 @@ -/******************************************************************************* - * tlx/die/core.hpp - * - * Part of tlx - http://panthema.net/tlx - * - * Copyright (C) 2016-2018 Timo Bingmann - * - * All rights reserved. Published under the Boost Software License, Version 1.0 - ******************************************************************************/ - -#ifndef TLX_DIE_CORE_HEADER -#define TLX_DIE_CORE_HEADER - -#include -#include -#include -#include -#include - -namespace tlx { - -/******************************************************************************/ -// die macros - -//! die with message - either throw an exception or die via std::terminate() -void die_with_message(const std::string &msg); - -//! die with message - either throw an exception or die via std::terminate() -void die_with_message(const char *msg, const char *file, size_t line); - -//! die with message - either throw an exception or die via std::terminate() -void die_with_message(const std::string &msg, const char *file, size_t line); - -//! Instead of std::terminate(), throw the output the message via an exception. -#define tlx_die_with_sstream(msg) \ - do { \ - std::ostringstream oss__; \ - oss__ << msg << " @ " << __FILE__ << ':' << __LINE__; \ - ::tlx::die_with_message(oss__.str()); \ - std::terminate(); /* tell compiler this never returns */ \ - } while (false) - -//! Instead of std::terminate(), throw the output the message via an exception. -#define tlx_die(msg) \ - do { \ - tlx_die_with_sstream("DIE: " << msg); \ - } while (false) - -//! Exception thrown by die_with_message() if -class DieException : public std::runtime_error { - public: - explicit DieException(const std::string &message); -}; - -//! Switch between dying via std::terminate() and throwing an exception. -//! Alternatively define the macro TLX_DIE_WITH_EXCEPTION=1 -bool set_die_with_exception(bool b); - -/******************************************************************************/ -// die_unless() and die_if() - -//! Check condition X and die miserably if false. Same as assert() except this -//! is also active in Release mode. -#define tlx_die_unless(X) \ - do { \ - if (!(X)) { \ - ::tlx::die_with_message("DIE: Assertion \"" #X "\" failed!", __FILE__, __LINE__); \ - } \ - } while (false) - -//! Check condition X and die miserably if true. Opposite of assert() except -//! this is also active in Release mode. -#define tlx_die_if(X) \ - do { \ - if (X) { \ - ::tlx::die_with_message("DIE: Assertion \"" #X "\" succeeded!", __FILE__, __LINE__); \ - } \ - } while (false) - -//! Check condition X and die miserably if false. Same as tlx_die_unless() -//! except the user additionally passes a message. -#define tlx_die_verbose_unless(X, msg) \ - do { \ - if (!(X)) { \ - tlx_die_with_sstream("DIE: Assertion \"" #X "\" failed!\n" << msg << '\n'); \ - } \ - } while (false) - -//! Check condition X and die miserably if false. Same as tlx_die_if() -//! except the user additionally passes a message. -#define tlx_die_verbose_if(X, msg) \ - do { \ - if ((X)) { \ - tlx_die_with_sstream("DIE: Assertion \"" #X "\" succeeded!\n" << msg << '\n'); \ - } \ - } while (false) - -/******************************************************************************/ -// die_unequal() - -//! helper method to compare two values in die_unequal() -template -inline bool die_equal_compare(TypeA a, TypeB b) { - return a == b; -} - -template <> -inline bool die_equal_compare(const char *a, const char *b) { - // compare string contents - return std::strcmp(a, b) == 0; -} - -template <> -inline bool die_equal_compare(float a, float b) { - // special case for NAN - return a != a ? b != b : a == b; -} - -template <> -inline bool die_equal_compare(double a, double b) { - // special case for NAN - return a != a ? b != b : a == b; -} - -//! Check that X == Y or die miserably, but output the values of X and Y for -//! better debugging. -#define tlx_die_unequal(X, Y) \ - do { \ - auto x__ = (X); /* NOLINT */ \ - auto y__ = (Y); /* NOLINT */ \ - if (!::tlx::die_equal_compare(x__, y__)) \ - tlx_die_with_sstream("DIE-UNEQUAL: " #X " != " #Y \ - " : " \ - "\"" \ - << x__ << "\" != \"" << y__ << "\""); \ - } while (false) - -//! Check that X == Y or die miserably, but output the values of X and Y for -//! better debugging. Only active if NDEBUG is not defined. -#ifdef NDEBUG -#define tlx_assert_equal(X, Y) -#else -#define tlx_assert_equal(X, Y) die_unequal(X, Y) -#endif - -//! Check that X == Y or die miserably, but output the values of X and Y for -//! better debugging. Same as tlx_die_unequal() except the user additionally -//! pass a message. -#define tlx_die_verbose_unequal(X, Y, msg) \ - do { \ - auto x__ = (X); /* NOLINT */ \ - auto y__ = (Y); /* NOLINT */ \ - if (!::tlx::die_equal_compare(x__, y__)) \ - tlx_die_with_sstream("DIE-UNEQUAL: " #X " != " #Y \ - " : " \ - "\"" \ - << x__ << "\" != \"" << y__ << "\"\n" \ - << msg << '\n'); \ - } while (false) - -/******************************************************************************/ -// die_unequal_eps() - -//! simple replacement for std::abs -template -inline Type die_unequal_eps_abs(const Type &t) { - return t < 0 ? -t : t; -} - -//! helper method to compare two values in die_unequal_eps() -template -inline bool die_equal_eps_compare(TypeA x, TypeB y, double eps) { - // special case for NAN - return x != x ? y != y : die_unequal_eps_abs(x - y) <= eps; -} - -//! Check that ABS(X - Y) <= eps or die miserably, but output the values of X -//! and Y for better debugging. -#define tlx_die_unequal_eps(X, Y, eps) \ - do { \ - auto x__ = (X); /* NOLINT */ \ - auto y__ = (Y); /* NOLINT */ \ - if (!::tlx::die_equal_eps_compare(x__, y__, eps)) \ - tlx_die("DIE-UNEQUAL-EPS: " #X " != " #Y " : " << std::setprecision(18) << "\"" << x__ << "\" != \"" << y__ \ - << "\""); \ - } while (false) - -//! Check that ABS(X - Y) <= eps or die miserably, but output the values of X -//! and Y for better debugging. Same as tlx_die_unequal_eps() except the user -//! additionally passes a message. -#define tlx_die_verbose_unequal_eps(X, Y, eps, msg) \ - do { \ - auto x__ = (X); /* NOLINT */ \ - auto y__ = (Y); /* NOLINT */ \ - if (!::tlx::die_equal_eps_compare(x__, y__, eps)) \ - tlx_die("DIE-UNEQUAL-EPS: " #X " != " #Y " : " << std::setprecision(18) << "\"" << x__ << "\" != \"" << y__ \ - << "\"\n" \ - << msg << '\n'); \ - } while (false) - -//! Check that ABS(X - Y) <= 0.000001 or die miserably, but output the values of -//! X and Y for better debugging. -#define tlx_die_unequal_eps6(X, Y) die_unequal_eps(X, Y, 1e-6) - -//! Check that ABS(X - Y) <= 0.000001 or die miserably, but output the values of -//! X and Y for better debugging. Same as tlx_die_unequal_eps6() except the user -//! additionally passes a message. -#define tlx_die_verbose_unequal_eps6(X, Y, msg) die_verbose_unequal_eps(X, Y, 1e-6, msg) - -/******************************************************************************/ -// die_equal() - -//! Die miserably if X == Y, but first output the values of X and Y for better -//! debugging. -#define tlx_die_equal(X, Y) \ - do { \ - auto x__ = (X); /* NOLINT */ \ - auto y__ = (Y); /* NOLINT */ \ - if (::tlx::die_equal_compare(x__, y__)) \ - tlx_die_with_sstream("DIE-EQUAL: " #X " == " #Y \ - " : " \ - "\"" \ - << x__ << "\" == \"" << y__ << "\""); \ - } while (false) - -//! Die miserably if X == Y, but first output the values of X and Y for better -//! debugging. Only active if NDEBUG is not defined. -#ifdef NDEBUG -#define tlx_assert_unequal(X, Y) -#else -#define tlx_assert_unequal(X, Y) die_equal(X, Y) -#endif - -//! Die miserably if X == Y, but first output the values of X and Y for better -//! debugging. Same as tlx_die_equal() except the user additionally passes a -//! message. -#define tlx_die_verbose_equal(X, Y, msg) \ - do { \ - auto x__ = (X); /* NOLINT */ \ - auto y__ = (Y); /* NOLINT */ \ - if (::tlx::die_equal_compare(x__, y__)) \ - tlx_die_with_sstream("DIE-EQUAL: " #X " == " #Y \ - " : " \ - "\"" \ - << x__ << "\" == \"" << y__ << "\"\n" \ - << msg << '\n'); \ - } while (false) - -/******************************************************************************/ -// die_unless_throws() - -//! Define to check that [code] throws and exception of given type -#define tlx_die_unless_throws(code, exception_type) \ - do { \ - try { \ - code; \ - } catch (const exception_type &) { \ - break; \ - } \ - ::tlx::die_with_message("DIE-UNLESS-THROWS: " #code " - NO EXCEPTION " #exception_type, __FILE__, __LINE__); \ - } while (false) - -} // namespace tlx - -#endif // !TLX_DIE_CORE_HEADER - -/******************************************************************************/ diff --git a/tests/benchmark/data_structures_common.hpp b/tests/benchmark/data_structures_common.hpp index fb9053c67..d6426dcfe 100644 --- a/tests/benchmark/data_structures_common.hpp +++ b/tests/benchmark/data_structures_common.hpp @@ -15,7 +15,6 @@ #include #include -#include "btree_map.hpp" #include "coordinator/hybrid_logical_clock.hpp" #include "storage/v3/key_store.hpp" #include "storage/v3/lexicographically_ordered_vertex.hpp" @@ -30,7 +29,6 @@ inline void PrepareData(utils::SkipList &skip_list, const int64_t num_element coordinator::Hlc start_timestamp; storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; storage::v3::Transaction transaction{start_timestamp, isolation_level}; - auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); for (auto i{0}; i < num_elements; ++i) { auto acc = skip_list.access(); acc.insert({storage::v3::PrimaryKey{storage::v3::PropertyValue{i}}}); @@ -55,22 +53,9 @@ inline void PrepareData(std::set &std_set, const int64_t num_elements) { coordinator::Hlc start_timestamp; storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; storage::v3::Transaction transaction{start_timestamp, isolation_level}; - auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); for (auto i{0}; i < num_elements; ++i) { std_set.insert(std::vector{storage::v3::PropertyValue{i}}); } } -template -inline void PrepareData(tlx::btree_map &bpp_tree, const int64_t num_elements) { - coordinator::Hlc start_timestamp; - storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; - storage::v3::Transaction transaction{start_timestamp, isolation_level}; - auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); - for (auto i{0}; i < num_elements; ++i) { - bpp_tree.insert({storage::v3::PrimaryKey{storage::v3::PropertyValue{i}}, - storage::v3::LexicographicallyOrderedVertex{storage::v3::Vertex{ - delta, std::vector{storage::v3::PropertyValue{i}}}}}); - } -} } // namespace memgraph::benchmark diff --git a/tests/benchmark/data_structures_contains.cpp b/tests/benchmark/data_structures_contains.cpp index 2950aeed3..47af24a1b 100644 --- a/tests/benchmark/data_structures_contains.cpp +++ b/tests/benchmark/data_structures_contains.cpp @@ -23,7 +23,6 @@ #include #include -#include "btree_map.hpp" #include "data_structures_common.hpp" #include "storage/v3/key_store.hpp" #include "storage/v3/lexicographically_ordered_vertex.hpp" @@ -79,10 +78,7 @@ static void BM_BenchmarkContainsStdMap(::benchmark::State &state) { static void BM_BenchmarkContainsStdSet(::benchmark::State &state) { std::set std_set; PrepareData(std_set, state.range(0)); - coordinator::Hlc start_timestamp; - storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; - storage::v3::Transaction transaction{start_timestamp, isolation_level}; - auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); + // So we can also have elements that does don't exist std::mt19937 i_generator(std::random_device{}()); std::uniform_int_distribution i_distribution(0, state.range(0) * 2); @@ -98,33 +94,12 @@ static void BM_BenchmarkContainsStdSet(::benchmark::State &state) { state.SetItemsProcessed(found_elems); } -static void BM_BenchmarkContainsBppTree(::benchmark::State &state) { - tlx::btree_map bpp_tree; - PrepareData(bpp_tree, state.range(0)); - - // So we can also have elements that does don't exists - std::mt19937 i_generator(std::random_device{}()); - std::uniform_int_distribution i_distribution(0, state.range(0) * 2); - int64_t found_elems{0}; - for (auto _ : state) { - for (auto i{0}; i < state.range(0); ++i) { - int64_t value = i_distribution(i_generator); - if (bpp_tree.count(storage::v3::PrimaryKey{{storage::v3::PropertyValue(value)}}) > 0) { - found_elems++; - } - } - } - state.SetItemsProcessed(found_elems); -} - BENCHMARK(BM_BenchmarkContainsSkipList)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond); BENCHMARK(BM_BenchmarkContainsStdMap)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond); BENCHMARK(BM_BenchmarkContainsStdSet)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond); -BENCHMARK(BM_BenchmarkContainsBppTree)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond); - } // namespace memgraph::benchmark BENCHMARK_MAIN(); diff --git a/tests/benchmark/data_structures_find.cpp b/tests/benchmark/data_structures_find.cpp index f1f1955c4..d51771be5 100644 --- a/tests/benchmark/data_structures_find.cpp +++ b/tests/benchmark/data_structures_find.cpp @@ -23,7 +23,6 @@ #include #include -#include "btree_map.hpp" #include "data_structures_common.hpp" #include "storage/v3/key_store.hpp" #include "storage/v3/lexicographically_ordered_vertex.hpp" @@ -78,10 +77,6 @@ static void BM_BenchmarkFindStdMap(::benchmark::State &state) { static void BM_BenchmarkFindStdSet(::benchmark::State &state) { std::set std_set; PrepareData(std_set, state.range(0)); - coordinator::Hlc start_timestamp; - storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; - storage::v3::Transaction transaction{start_timestamp, isolation_level}; - auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); // So we can also have elements that does don't exist std::mt19937 i_generator(std::random_device{}()); @@ -98,32 +93,12 @@ static void BM_BenchmarkFindStdSet(::benchmark::State &state) { state.SetItemsProcessed(found_elems); } -static void BM_BenchmarkFindBppTree(::benchmark::State &state) { - tlx::btree_map bpp_tree; - PrepareData(bpp_tree, state.range(0)); - // So we can also have elements that does don't exist - std::mt19937 i_generator(std::random_device{}()); - std::uniform_int_distribution i_distribution(0, state.range(0) * 2); - int64_t found_elems{0}; - for (auto _ : state) { - for (auto i{0}; i < state.range(0); ++i) { - int64_t value = i_distribution(i_generator); - if (bpp_tree.find(storage::v3::PrimaryKey{{storage::v3::PropertyValue(value)}}) != bpp_tree.end()) { - found_elems++; - } - } - } - state.SetItemsProcessed(found_elems); -} - BENCHMARK(BM_BenchmarkFindSkipList)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond); BENCHMARK(BM_BenchmarkFindStdMap)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond); BENCHMARK(BM_BenchmarkFindStdSet)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond); -BENCHMARK(BM_BenchmarkFindBppTree)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond); - } // namespace memgraph::benchmark BENCHMARK_MAIN(); diff --git a/tests/benchmark/data_structures_insert.cpp b/tests/benchmark/data_structures_insert.cpp index 1e5361d62..6700eaa93 100644 --- a/tests/benchmark/data_structures_insert.cpp +++ b/tests/benchmark/data_structures_insert.cpp @@ -23,7 +23,6 @@ #include #include -#include "btree_map.hpp" #include "storage/v3/key_store.hpp" #include "storage/v3/lexicographically_ordered_vertex.hpp" #include "storage/v3/mvcc.hpp" @@ -82,30 +81,12 @@ static void BM_BenchmarkInsertStdSet(::benchmark::State &state) { } } -static void BM_BenchmarkInsertBppTree(::benchmark::State &state) { - tlx::btree_map bpp_tree; - coordinator::Hlc start_timestamp; - storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; - storage::v3::Transaction transaction{start_timestamp, isolation_level}; - auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); - - for (auto _ : state) { - for (auto i{0}; i < state.range(0); ++i) { - bpp_tree.insert({storage::v3::PrimaryKey{storage::v3::PropertyValue{i}}, - storage::v3::LexicographicallyOrderedVertex{storage::v3::Vertex{ - delta, std::vector{storage::v3::PropertyValue{i}}}}}); - } - } -} - BENCHMARK(BM_BenchmarkInsertSkipList)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond); BENCHMARK(BM_BenchmarkInsertStdMap)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond); BENCHMARK(BM_BenchmarkInsertStdSet)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond); -BENCHMARK(BM_BenchmarkInsertBppTree)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond); - } // namespace memgraph::benchmark BENCHMARK_MAIN(); diff --git a/tests/benchmark/data_structures_remove.cpp b/tests/benchmark/data_structures_remove.cpp index 17dbd1dec..0d7baaccf 100644 --- a/tests/benchmark/data_structures_remove.cpp +++ b/tests/benchmark/data_structures_remove.cpp @@ -23,7 +23,6 @@ #include #include -#include "btree_map.hpp" #include "data_structures_common.hpp" #include "storage/v3/key_store.hpp" #include "storage/v3/lexicographically_ordered_vertex.hpp" @@ -41,10 +40,6 @@ namespace memgraph::benchmark { static void BM_BenchmarkRemoveSkipList(::benchmark::State &state) { utils::SkipList skip_list; PrepareData(skip_list, state.range(0)); - coordinator::Hlc start_timestamp; - storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; - storage::v3::Transaction transaction{start_timestamp, isolation_level}; - auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); // So we can also have elements that does don't exist std::mt19937 i_generator(std::random_device{}()); @@ -84,10 +79,6 @@ static void BM_BenchmarkRemoveStdMap(::benchmark::State &state) { static void BM_BenchmarkRemoveStdSet(::benchmark::State &state) { std::set std_set; PrepareData(std_set, state.range(0)); - coordinator::Hlc start_timestamp; - storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; - storage::v3::Transaction transaction{start_timestamp, isolation_level}; - auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); // So we can also have elements that does don't exist std::mt19937 i_generator(std::random_device{}()); @@ -104,33 +95,12 @@ static void BM_BenchmarkRemoveStdSet(::benchmark::State &state) { state.SetItemsProcessed(removed_elems); } -static void BM_BenchmarkRemoveBppTree(::benchmark::State &state) { - tlx::btree_map bpp_tree; - PrepareData(bpp_tree, state.range(0)); - - // So we can also have elements that does don't exist - std::mt19937 i_generator(std::random_device{}()); - std::uniform_int_distribution i_distribution(0, state.range(0) * 2); - int64_t removed_elems{0}; - for (auto _ : state) { - for (auto i{0}; i < state.range(0); ++i) { - int64_t value = i_distribution(i_generator); - if (bpp_tree.erase(storage::v3::PrimaryKey{storage::v3::PropertyValue{value}}) > 0) { - removed_elems++; - } - } - } - state.SetItemsProcessed(removed_elems); -} - BENCHMARK(BM_BenchmarkRemoveSkipList)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond); BENCHMARK(BM_BenchmarkRemoveStdMap)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond); BENCHMARK(BM_BenchmarkRemoveStdSet)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond); -BENCHMARK(BM_BenchmarkRemoveBppTree)->RangeMultiplier(10)->Range(1000, 10000000)->Unit(::benchmark::kMillisecond); - } // namespace memgraph::benchmark BENCHMARK_MAIN(); From 1b97bb085678e8be8e9a122b006e13c6ba5bdc87 Mon Sep 17 00:00:00 2001 From: jbajic Date: Tue, 29 Nov 2022 14:57:53 +0100 Subject: [PATCH 13/16] Remove redundant line from skiplist --- src/utils/skip_list.hpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/utils/skip_list.hpp b/src/utils/skip_list.hpp index 7d4897884..486aade57 100644 --- a/src/utils/skip_list.hpp +++ b/src/utils/skip_list.hpp @@ -621,8 +621,6 @@ class SkipList final { TNode *node_; }; - using iterator = Iterator; - class Accessor final { private: friend class SkipList; From 9bab26fb105752005745f0beacd36b3e5a2f6053 Mon Sep 17 00:00:00 2001 From: Jure Bajic Date: Mon, 5 Dec 2022 15:50:40 +0100 Subject: [PATCH 14/16] Apply suggestions from code review Co-authored-by: Kostas Kyrimis --- tests/benchmark/data_structures_remove.cpp | 2 +- tests/benchmark/skip_list_common.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/benchmark/data_structures_remove.cpp b/tests/benchmark/data_structures_remove.cpp index 0d7baaccf..641ad9453 100644 --- a/tests/benchmark/data_structures_remove.cpp +++ b/tests/benchmark/data_structures_remove.cpp @@ -41,7 +41,7 @@ static void BM_BenchmarkRemoveSkipList(::benchmark::State &state) { utils::SkipList skip_list; PrepareData(skip_list, state.range(0)); - // So we can also have elements that does don't exist + // So we can also have elements that don't exist std::mt19937 i_generator(std::random_device{}()); std::uniform_int_distribution i_distribution(0, state.range(0) * 2); int64_t removed_elems{0}; diff --git a/tests/benchmark/skip_list_common.hpp b/tests/benchmark/skip_list_common.hpp index 8dd4c705b..54e62ed7e 100644 --- a/tests/benchmark/skip_list_common.hpp +++ b/tests/benchmark/skip_list_common.hpp @@ -99,7 +99,7 @@ inline void RunTest(std::function &, Stats &)> test std::cout << " Successful find: " << stats.succ[3] << std::endl; std::cout << std::endl; - const auto tot = stats.succ[0] + stats.succ[1] + stats.succ[2] + stats.succ[3]; + const auto tot = std::accumulate(stats.succ.begin(), + stats.succ.begin() + 3, 0); const auto tops = stats.total; std::cout << "Total successful: " << tot << " (" << tot / FLAGS_duration << " calls/s)" << std::endl; From e0b7d7abebb25a406bb430815b941143f2dd44f8 Mon Sep 17 00:00:00 2001 From: jbajic Date: Mon, 5 Dec 2022 16:00:17 +0100 Subject: [PATCH 15/16] Address review comments --- tests/benchmark/data_structures_common.hpp | 9 +++------ tests/benchmark/data_structures_insert.cpp | 11 ++--------- tests/benchmark/skip_list_common.hpp | 7 +++++-- 3 files changed, 10 insertions(+), 17 deletions(-) diff --git a/tests/benchmark/data_structures_common.hpp b/tests/benchmark/data_structures_common.hpp index d6426dcfe..56c4d60cd 100644 --- a/tests/benchmark/data_structures_common.hpp +++ b/tests/benchmark/data_structures_common.hpp @@ -27,8 +27,7 @@ namespace memgraph::benchmark { template inline void PrepareData(utils::SkipList &skip_list, const int64_t num_elements) { coordinator::Hlc start_timestamp; - storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; - storage::v3::Transaction transaction{start_timestamp, isolation_level}; + storage::v3::Transaction transaction{start_timestamp, storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; for (auto i{0}; i < num_elements; ++i) { auto acc = skip_list.access(); acc.insert({storage::v3::PrimaryKey{storage::v3::PropertyValue{i}}}); @@ -38,8 +37,7 @@ inline void PrepareData(utils::SkipList &skip_list, const int64_t num_element template inline void PrepareData(std::map &std_map, const int64_t num_elements) { coordinator::Hlc start_timestamp; - storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; - storage::v3::Transaction transaction{start_timestamp, isolation_level}; + storage::v3::Transaction transaction{start_timestamp, storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); for (auto i{0}; i < num_elements; ++i) { std_map.insert({storage::v3::PrimaryKey{storage::v3::PropertyValue{i}}, @@ -51,8 +49,7 @@ inline void PrepareData(std::map &std_map, const int64_t num_eleme template inline void PrepareData(std::set &std_set, const int64_t num_elements) { coordinator::Hlc start_timestamp; - storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; - storage::v3::Transaction transaction{start_timestamp, isolation_level}; + storage::v3::Transaction transaction{start_timestamp, storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; for (auto i{0}; i < num_elements; ++i) { std_set.insert(std::vector{storage::v3::PropertyValue{i}}); } diff --git a/tests/benchmark/data_structures_insert.cpp b/tests/benchmark/data_structures_insert.cpp index 6700eaa93..6469c8ea3 100644 --- a/tests/benchmark/data_structures_insert.cpp +++ b/tests/benchmark/data_structures_insert.cpp @@ -39,8 +39,7 @@ namespace memgraph::benchmark { static void BM_BenchmarkInsertSkipList(::benchmark::State &state) { utils::SkipList skip_list; coordinator::Hlc start_timestamp; - storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; - storage::v3::Transaction transaction{start_timestamp, isolation_level}; + storage::v3::Transaction transaction{start_timestamp, storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); for (auto _ : state) { @@ -54,8 +53,7 @@ static void BM_BenchmarkInsertSkipList(::benchmark::State &state) { static void BM_BenchmarkInsertStdMap(::benchmark::State &state) { std::map std_map; coordinator::Hlc start_timestamp; - storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; - storage::v3::Transaction transaction{start_timestamp, isolation_level}; + storage::v3::Transaction transaction{start_timestamp, storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); for (auto _ : state) { @@ -69,11 +67,6 @@ static void BM_BenchmarkInsertStdMap(::benchmark::State &state) { static void BM_BenchmarkInsertStdSet(::benchmark::State &state) { std::set std_set; - coordinator::Hlc start_timestamp; - storage::v3::IsolationLevel isolation_level{storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; - storage::v3::Transaction transaction{start_timestamp, isolation_level}; - auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); - for (auto _ : state) { for (auto i{0}; i < state.range(0); ++i) { std_set.insert(storage::v3::PrimaryKey{std::vector{storage::v3::PropertyValue{i}}}); diff --git a/tests/benchmark/skip_list_common.hpp b/tests/benchmark/skip_list_common.hpp index 54e62ed7e..4b27281c8 100644 --- a/tests/benchmark/skip_list_common.hpp +++ b/tests/benchmark/skip_list_common.hpp @@ -11,11 +11,14 @@ #pragma once +#include #include #include +#include #include #include #include +#include #include #include @@ -26,7 +29,7 @@ DEFINE_int32(duration, 10, "Duration of test (in seconds)"); struct Stats { uint64_t total{0}; - uint64_t succ[4] = {0, 0, 0, 0}; + std::array succ = {0, 0, 0, 0}; }; const int OP_INSERT = 0; @@ -99,7 +102,7 @@ inline void RunTest(std::function &, Stats &)> test std::cout << " Successful find: " << stats.succ[3] << std::endl; std::cout << std::endl; - const auto tot = std::accumulate(stats.succ.begin(), + stats.succ.begin() + 3, 0); + const auto tot = std::accumulate(stats.succ.begin(), +stats.succ.begin() + 3, 0); const auto tops = stats.total; std::cout << "Total successful: " << tot << " (" << tot / FLAGS_duration << " calls/s)" << std::endl; From b4d6ca22336d58a93d57a863cdb91f1c1fd2b527 Mon Sep 17 00:00:00 2001 From: jbajic Date: Tue, 6 Dec 2022 11:47:48 +0100 Subject: [PATCH 16/16] Use bool as data --- tests/benchmark/data_structures_common.hpp | 6 +++--- tests/benchmark/data_structures_insert.cpp | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/benchmark/data_structures_common.hpp b/tests/benchmark/data_structures_common.hpp index 56c4d60cd..23fe394ee 100644 --- a/tests/benchmark/data_structures_common.hpp +++ b/tests/benchmark/data_structures_common.hpp @@ -30,7 +30,7 @@ inline void PrepareData(utils::SkipList &skip_list, const int64_t num_element storage::v3::Transaction transaction{start_timestamp, storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; for (auto i{0}; i < num_elements; ++i) { auto acc = skip_list.access(); - acc.insert({storage::v3::PrimaryKey{storage::v3::PropertyValue{i}}}); + acc.insert({storage::v3::PrimaryKey{storage::v3::PropertyValue{true}}}); } } @@ -42,7 +42,7 @@ inline void PrepareData(std::map &std_map, const int64_t num_eleme for (auto i{0}; i < num_elements; ++i) { std_map.insert({storage::v3::PrimaryKey{storage::v3::PropertyValue{i}}, storage::v3::LexicographicallyOrderedVertex{storage::v3::Vertex{ - delta, std::vector{storage::v3::PropertyValue{i}}}}}); + delta, std::vector{storage::v3::PropertyValue{true}}}}}); } } @@ -51,7 +51,7 @@ inline void PrepareData(std::set &std_set, const int64_t num_elements) { coordinator::Hlc start_timestamp; storage::v3::Transaction transaction{start_timestamp, storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; for (auto i{0}; i < num_elements; ++i) { - std_set.insert(std::vector{storage::v3::PropertyValue{i}}); + std_set.insert(std::vector{storage::v3::PropertyValue{true}}); } } diff --git a/tests/benchmark/data_structures_insert.cpp b/tests/benchmark/data_structures_insert.cpp index 6469c8ea3..d83cbfe23 100644 --- a/tests/benchmark/data_structures_insert.cpp +++ b/tests/benchmark/data_structures_insert.cpp @@ -40,12 +40,11 @@ static void BM_BenchmarkInsertSkipList(::benchmark::State &state) { utils::SkipList skip_list; coordinator::Hlc start_timestamp; storage::v3::Transaction transaction{start_timestamp, storage::v3::IsolationLevel::SNAPSHOT_ISOLATION}; - auto *delta = storage::v3::CreateDeleteObjectDelta(&transaction); for (auto _ : state) { for (auto i{0}; i < state.range(0); ++i) { auto acc = skip_list.access(); - acc.insert({storage::v3::PrimaryKey{storage::v3::PropertyValue{i}}}); + acc.insert({storage::v3::PrimaryKey{storage::v3::PropertyValue{true}}}); } } } @@ -60,7 +59,7 @@ static void BM_BenchmarkInsertStdMap(::benchmark::State &state) { for (auto i{0}; i < state.range(0); ++i) { std_map.insert({storage::v3::PrimaryKey{storage::v3::PropertyValue{i}}, storage::v3::LexicographicallyOrderedVertex{storage::v3::Vertex{ - delta, std::vector{storage::v3::PropertyValue{i}}}}}); + delta, std::vector{storage::v3::PropertyValue{true}}}}}); } } } @@ -69,7 +68,8 @@ static void BM_BenchmarkInsertStdSet(::benchmark::State &state) { std::set std_set; for (auto _ : state) { for (auto i{0}; i < state.range(0); ++i) { - std_set.insert(storage::v3::PrimaryKey{std::vector{storage::v3::PropertyValue{i}}}); + std_set.insert( + storage::v3::PrimaryKey{std::vector{storage::v3::PropertyValue{true}}}); } } }