/* * Souffle - A Datalog Compiler * Copyright (c) 2018, Souffle Developers * Licensed under the Universal Permissive License v 1.0 as shown at: * - https://opensource.org/licenses/UPL * - /licenses/SOUFFLE-UPL.txt */ /************************************************************************ * * @file LambdaBTree.h * * An implementation of a generic B-tree data structure including * interfaces for utilizing instances as set or multiset containers. * Allows the user to provide a function to execute on successful insert * Be careful using this, it currently expects a pair as the key. * ***********************************************************************/ #pragma once #include "souffle/datastructure/BTree.h" #include "souffle/utility/ContainerUtil.h" #include "souffle/utility/ParallelUtil.h" #include #include #include #include namespace souffle { namespace detail { /** * The actual implementation of a b-tree data structure. * * @tparam Key .. the element type to be stored in this tree * @tparam Comparator .. a class defining an order on the stored elements * @tparam Allocator .. utilized for allocating memory for required nodes * @tparam blockSize .. determines the number of bytes/block utilized by leaf nodes * @tparam SearchStrategy .. enables switching between linear, binary or any other search strategy * @tparam isSet .. true = set, false = multiset * @tparam Functor .. a std::function that is called on successful (new) insert */ template > class LambdaBTree : public btree { public: using parenttype = btree; LambdaBTree(const Comparator& comp = Comparator(), const WeakComparator& weak_comp = WeakComparator()) : parenttype(comp, weak_comp) {} /** * Inserts the given key into this tree. */ typename Functor::result_type insert(Key& k, const Functor& f) { typename parenttype::operation_hints hints; return insert(k, hints, f); } // rewriting this because of david's changes typename Functor::result_type insert( Key& k, typename parenttype::operation_hints& hints, const Functor& f) { #ifdef IS_PARALLEL // special handling for inserting first element while (this->root == nullptr) { // try obtaining root-lock if (!this->root_lock.try_start_write()) { // somebody else was faster => re-check continue; } // check loop condition again if (this->root != nullptr) { // somebody else was faster => normal insert this->root_lock.abort_write(); break; } // create new node this->leftmost = new typename parenttype::leaf_node(); this->leftmost->numElements = 1; // call the functor as we've successfully inserted typename Functor::result_type res = f(k); this->leftmost->keys[0] = k; this->root = this->leftmost; // operation complete => we can release the root lock this->root_lock.end_write(); hints.last_insert.access(this->leftmost); return res; } // insert using iterative implementation typename parenttype::node* cur = nullptr; // test last insert hints typename parenttype::lock_type::Lease cur_lease; auto checkHint = [&](typename parenttype::node* last_insert) { // ignore null pointer if (!last_insert) return false; // get a read lease on indicated node auto hint_lease = last_insert->lock.start_read(); // check whether it covers the key if (!this->weak_covers(last_insert, k)) return false; // and if there was no concurrent modification if (!last_insert->lock.validate(hint_lease)) return false; // use hinted location cur = last_insert; // and keep lease cur_lease = hint_lease; // we found a hit return true; }; if (hints.last_insert.any(checkHint)) { // register this as a hit this->hint_stats.inserts.addHit(); } else { // register this as a miss this->hint_stats.inserts.addMiss(); } // if there is no valid hint .. if (!cur) { do { // get root - access lock auto root_lease = this->root_lock.start_read(); // start with root cur = this->root; // get lease of the next node to be accessed cur_lease = cur->lock.start_read(); // check validity of root pointer if (this->root_lock.end_read(root_lease)) { break; } } while (true); } while (true) { // handle inner nodes if (cur->inner) { auto a = &(cur->keys[0]); auto b = &(cur->keys[cur->numElements]); auto pos = this->search.lower_bound(k, a, b, this->weak_comp); auto idx = pos - a; // early exit for sets if (isSet && pos != b && this->weak_equal(*pos, k)) { // validate results if (!cur->lock.validate(cur_lease)) { // start over again return insert(k, hints, f); } // update provenance information if (typeid(Comparator) != typeid(WeakComparator) && this->less(k, *pos)) { if (!cur->lock.try_upgrade_to_write(cur_lease)) { // start again return insert(k, hints, f); } this->update(*pos, k); // get result before releasing lock auto res = (*pos).second; cur->lock.end_write(); return res; } // get the result before releasing lock auto res = (*pos).second; // check validity if (!cur->lock.validate(cur_lease)) { // start over again return insert(k, hints, f); } // we found the element => return the result return res; } // get next pointer auto next = cur->getChild(idx); // get lease on next level auto next_lease = next->lock.start_read(); // check whether there was a write if (!cur->lock.end_read(cur_lease)) { // start over return insert(k, hints, f); } // go to next cur = next; // move on lease cur_lease = next_lease; continue; } // the rest is for leaf nodes assert(!cur->inner); // -- insert node in leaf node -- auto a = &(cur->keys[0]); auto b = &(cur->keys[cur->numElements]); auto pos = this->search.upper_bound(k, a, b, this->weak_comp); auto idx = pos - a; // early exit for sets if (isSet && pos != a && this->weak_equal(*(pos - 1), k)) { // validate result if (!cur->lock.validate(cur_lease)) { // start over again return insert(k, hints, f); } // TODO (pnappa): remove provenance from LambdaBTree - no use for it // update provenance information if (typeid(Comparator) != typeid(WeakComparator) && this->less(k, *(pos - 1))) { if (!cur->lock.try_upgrade_to_write(cur_lease)) { // start again return insert(k, hints, f); } this->update(*(pos - 1), k); // retrieve result before releasing lock auto res = (*(pos - 1)).second; cur->lock.end_write(); return res; } // read result (atomic) -- just as a proof of concept, this is actually not valid!! std::atomic& loc = *reinterpret_cast*>(&(*(pos - 1)).second); auto res = loc.load(std::memory_order_relaxed); // check validity if (!cur->lock.validate(cur_lease)) { // start over again return insert(k, hints, f); } // we found the element => done return res; } // upgrade to write-permission if (!cur->lock.try_upgrade_to_write(cur_lease)) { // something has changed => restart hints.last_insert.access(cur); return insert(k, hints, f); } if (cur->numElements >= parenttype::node::maxKeys) { // -- lock parents -- auto priv = cur; auto parent = priv->parent; std::vector parents; do { if (parent) { parent->lock.start_write(); while (true) { // check whether parent is correct if (parent == priv->parent) { break; } // switch parent parent->lock.abort_write(); parent = priv->parent; parent->lock.start_write(); } } else { // lock root lock => since cur is root this->root_lock.start_write(); } // record locked node parents.push_back(parent); // stop at "sphere of influence" if (!parent || !parent->isFull()) { break; } // go one step higher priv = parent; parent = parent->parent; } while (true); // split this node auto old_root = this->root; idx -= cur->rebalance_or_split(const_cast(&this->root), this->root_lock, static_cast(idx), parents); // release parent lock for (auto it = parents.rbegin(); it != parents.rend(); ++it) { auto parent = *it; // release this lock if (parent) { parent->lock.end_write(); } else { if (old_root != this->root) { this->root_lock.end_write(); } else { this->root_lock.abort_write(); } } } // insert element in right fragment if (((typename parenttype::size_type)idx) > cur->numElements) { // release current lock cur->lock.end_write(); // insert in sibling return insert(k, hints, f); } } // ok - no split necessary assert(cur->numElements < parenttype::node::maxKeys && "Split required!"); // move keys for (int j = static_cast(cur->numElements); j > idx; --j) { cur->keys[j] = cur->keys[j - 1]; } // insert new element typename Functor::result_type res = f(k); cur->keys[idx] = k; cur->numElements++; // release lock on current node cur->lock.end_write(); // remember last insertion position hints.last_insert.access(cur); return res; } #else // special handling for inserting first element if (this->empty()) { // create new node this->leftmost = new typename parenttype::leaf_node(); this->leftmost->numElements = 1; // call the functor as we've successfully inserted typename Functor::result_type res = f(k); this->leftmost->keys[0] = k; this->root = this->leftmost; hints.last_insert.access(this->leftmost); return res; } // insert using iterative implementation typename parenttype::node* cur = this->root; auto checkHints = [&](typename parenttype::node* last_insert) { if (!last_insert) return false; if (!this->weak_covers(last_insert, k)) return false; cur = last_insert; return true; }; // test last insert if (hints.last_insert.any(checkHints)) { this->hint_stats.inserts.addHit(); } else { this->hint_stats.inserts.addMiss(); } while (true) { // handle inner nodes if (cur->inner) { auto a = &(cur->keys[0]); auto b = &(cur->keys[cur->numElements]); auto pos = this->search.lower_bound(k, a, b, this->weak_comp); auto idx = pos - a; // early exit for sets if (isSet && pos != b && this->weak_equal(*pos, k)) { // update provenance information if (typeid(Comparator) != typeid(WeakComparator) && this->less(k, *pos)) { this->update(*pos, k); return (*pos).second; } return (*pos).second; } cur = cur->getChild(idx); continue; } // the rest is for leaf nodes assert(!cur->inner); // -- insert node in leaf node -- auto a = &(cur->keys[0]); auto b = &(cur->keys[cur->numElements]); auto pos = this->search.upper_bound(k, a, b, this->weak_comp); auto idx = pos - a; // early exit for sets if (isSet && pos != a && this->weak_equal(*(pos - 1), k)) { // update provenance information if (typeid(Comparator) != typeid(WeakComparator) && this->less(k, *(pos - 1))) { this->update(*(pos - 1), k); return (*(pos - 1)).second; } return (*(pos - 1)).second; } if (cur->numElements >= parenttype::node::maxKeys) { // split this node idx -= cur->rebalance_or_split(const_cast(&this->root), this->root_lock, static_cast(idx)); // insert element in right fragment if (((typename parenttype::size_type)idx) > cur->numElements) { idx -= cur->numElements + 1; cur = cur->parent->getChild(cur->position + 1); } } // ok - no split necessary assert(cur->numElements < parenttype::node::maxKeys && "Split required!"); // move keys for (int j = cur->numElements; j > idx; --j) { cur->keys[j] = cur->keys[j - 1]; } // call the functor as we've successfully inserted typename Functor::result_type res = f(k); // insert new element cur->keys[idx] = k; cur->numElements++; // remember last insertion position hints.last_insert.access(cur); return res; } #endif } /** * Inserts the given range of elements into this tree. */ template void insert(const Iter& a, const Iter& b) { // TODO: improve this beyond a naive insert typename parenttype::operation_hints hints; // a naive insert so far .. seems to work fine for (auto it = a; it != b; ++it) { // use insert with hint insert(*it, hints); } } /** * Swaps the content of this tree with the given tree. This * is a much more efficient operation than creating a copy and * realizing the swap utilizing assignment operations. */ void swap(LambdaBTree& other) { // swap the content std::swap(this->root, other.root); std::swap(this->leftmost, other.leftmost); } // Implementation of the assignment operation for trees. LambdaBTree& operator=(const LambdaBTree& other) { // check identity if (this == &other) { return *this; } // create a deep-copy of the content of the other tree // shortcut for empty sets if (other.empty()) { return *this; } // clone content (deep copy) this->root = other.root->clone(); // update leftmost reference auto tmp = this->root; while (!tmp->isLeaf()) { tmp = tmp->getChild(0); } this->leftmost = static_cast(tmp); // done return *this; } // Implementation of an equality operation for trees. bool operator==(const LambdaBTree& other) const { // check identity if (this == &other) { return true; } // check size if (this->size() != other.size()) { return false; } if (this->size() < other.size()) { return other == *this; } // check content for (const auto& key : other) { if (!contains(key)) { return false; } } return true; } // Implementation of an inequality operation for trees. bool operator!=(const LambdaBTree& other) const { return !(*this == other); } }; } // end namespace detail /** * A b-tree based set implementation. * * @tparam Key .. the element type to be stored in this set * @tparam Functor .. a std::function that is invoked on successful insert * @tparam Comparator .. a class defining an order on the stored elements * @tparam Allocator .. utilized for allocating memory for required nodes * @tparam blockSize .. determines the number of bytes/block utilized by leaf nodes * @tparam SearchStrategy .. enables switching between linear, binary or any other search strategy */ template , typename Allocator = std::allocator, // is ignored so far unsigned blockSize = 256, typename SearchStrategy = typename detail::default_strategy::type> class LambdaBTreeSet : public detail::LambdaBTree { using super = detail::LambdaBTree; friend class detail::LambdaBTree; public: /** * A default constructor creating an empty set. */ LambdaBTreeSet(const Comparator& comp = Comparator()) : super(comp) {} /** * A constructor creating a set based on the given range. */ template LambdaBTreeSet(const Iter& a, const Iter& b) { this->insert(a, b); } // A copy constructor. LambdaBTreeSet(const LambdaBTreeSet& other) : super(other) {} // A move constructor. LambdaBTreeSet(LambdaBTreeSet&& other) : super(std::move(other)) {} private: // A constructor required by the bulk-load facility. template LambdaBTreeSet(s size, n* root, l* leftmost) : super::parenttype(size, root, leftmost) {} public: // Support for the assignment operator. LambdaBTreeSet& operator=(const LambdaBTreeSet& other) { super::operator=(other); return *this; } // Support for the bulk-load operator. template static LambdaBTreeSet load(const Iter& a, const Iter& b) { return super::template load(a, b); } }; } // end of namespace souffle