diff --git a/Src/Adaptive/new/FNode.hpp b/Src/Adaptive/new/FNode.hpp index 560ed577ea3e89e91504ddd061b90e48557e7b40..3b966ea8666b27b523d97ac9105697d605a234b3 100644 --- a/Src/Adaptive/new/FNode.hpp +++ b/Src/Adaptive/new/FNode.hpp @@ -691,12 +691,11 @@ private: /** Allocates this node's children */ void create_children() { - using uninit_FNode = typename std::aligned_storage<sizeof(FNode)>::type; std::size_t idx = 0; // Remove this node from tree leaf list getTree().leaves().erase(this); // Create the children, add them to tree leaf list - FNode* tmp = reinterpret_cast<FNode*>(new uninit_FNode[child_count]); + FNode* tmp = this->getTree().node_memory_manager.provide(this->getDepth()+1, child_count); for(FNode*& child : getChildren()) { child = new(tmp+idx) FNode(*this, idx); getTree().leaves().insert(child); @@ -710,7 +709,6 @@ private: /* Deletes this node's children */ void delete_children() { - using uninit_FNode = typename std::aligned_storage<sizeof(FNode)>::type; // Remove children from tree leaf list, free them for(FNode*& child : getChildren()) { if(child) { @@ -718,7 +716,6 @@ private: child->~FNode(); } } - delete[] reinterpret_cast<uninit_FNode*>(getChild(0)); std::fill_n(this->getChildren().data(), child_count, nullptr); // Insert this node in tree leaf list getTree().leaves().insert(this); diff --git a/Src/Adaptive/new/FTree.hpp b/Src/Adaptive/new/FTree.hpp index 2102cebfb4c8723f2dfab1326a5cadfdd1f337ed..7814e8411711a29df367e37bd007f9cc0601a7fe 100644 --- a/Src/Adaptive/new/FTree.hpp +++ b/Src/Adaptive/new/FTree.hpp @@ -9,6 +9,7 @@ #include "FNodeIteratorBox.hpp" #include "FInOrderNodeIterator.hpp" #include "FPrePostOrderNodeIterator.hpp" +#include "UninitNodeMemoryManager.hpp" /** \brief Adaptive FMM tree * @@ -72,10 +73,13 @@ private: /// Tree height std::size_t _height = 0; + UninitNodeMemoryManager<node_t> node_memory_manager; + public: /** \brief Swaps two trees */ void swap(FTree& tree) { using std::swap; + swap(node_memory_manager, tree.node_memory_manager); swap(_max_height, tree._max_height); swap(_box, tree._box); swap(_root, tree._root); @@ -96,7 +100,8 @@ public: * */ FTree(box_t box_) : _box(box_) { - _root = new node_t(this); + _root = this->node_memory_manager.provide(0,1); + new(this->_root) node_t(this); } FTree(FTree&) = delete; @@ -115,7 +120,7 @@ public: /** \brief Destructor */ ~FTree() { - delete _root; + _root->~node_t(); } /** \brief Tree height diff --git a/Src/Adaptive/new/UninitNodeMemoryManager.hpp b/Src/Adaptive/new/UninitNodeMemoryManager.hpp new file mode 100644 index 0000000000000000000000000000000000000000..781cea2abef8e97bb6f053a5d97fc52d672b918a --- /dev/null +++ b/Src/Adaptive/new/UninitNodeMemoryManager.hpp @@ -0,0 +1,99 @@ + +#ifndef UNINITNODEMEMORYMANAGER_HPP +#define UNINITNODEMEMORYMANAGER_HPP + +#include <functional> +#include <list> +#include <vector> + +#include "UninitialisedMemoryProvider.hpp" + +/** + * \brief Manages uninitialised memory for nodes + * + * This class' intent is to provide block memory management for nodes. This + * reduces the amount of memory allocations. + * + * The nodes' memory is handled per level. Memory for a level is not garanteed + * to be contiguous, it is however contiguous by block. + * + * The memory requests are forwarded to UninitialisedMemoryProvider objects. A + * vector of list of providers is kept to ensure constant access to a level. The + * lists allow constant access to their last provider and allow adding new ones + * without reallocating the entire list. + * + * Memory is alway requested from the last provider in a list. When the last + * provider does not have enough memory available, a new one is appended to the + * list. + * + * \tparam _Node Node type + */ +template<class _Node> +class UninitNodeMemoryManager { + /** + * \brief Policy for memory provider capacity + * + * \param level Level of the nodes in the tree + * \param count Node count + * + * \return The provider size + */ + std::function<std::size_t(std::size_t level, std::size_t count)> + provider_size_policy = [](const std::size_t level, const std::size_t count) { + enum {max_level = 4}; + return std::max(std::size_t(1) << 3*(level > max_level ? max_level : level), count); + }; + + /** + * \brief memory provider lists + * + * The vector allows constant access to any level, the lists allow constant + * access to the last element. + */ + std::vector<std::list<UninitialisedMemoryProvider<_Node>>> providers; + + /** + * \brief Fill the providers vector until given level is reached + * + * \param level Tree level + */ + void add_until_level(const std::size_t level) noexcept { + const std::size_t provider_count = this->providers.size(); + if(level < provider_count) { + return; + } + this->providers.resize(level+1); + for(std::size_t l = provider_count; l < level+1; ++l) { + this->providers[l].emplace_back(provider_size_policy(level, 0)); + } + } + +public: + + /** + * \brief Provide uninitialised memory for given node count and tree level + * + * \param level Node level in the tree + * \param count Node count + * + * \return A pointer to the first node. Memory is uninitialised. + */ + _Node* provide(const std::size_t level, const std::size_t count) noexcept { + // Check that a provider for given level exists + if(this->providers.size() < level+1) { + this->add_until_level(level+1); + } + + // Add a provider when needed + if(! this->providers[level].back().can_provide(count)) { + this->providers[level].emplace_back(provider_size_policy(level, count)); + } + + return this->providers[level].back().provide(count); + } + + +}; + + +#endif /* UNINITNODEMEMORYMANAGER_HPP */ diff --git a/Src/Adaptive/new/UninitialisedMemoryProvider.hpp b/Src/Adaptive/new/UninitialisedMemoryProvider.hpp new file mode 100644 index 0000000000000000000000000000000000000000..23ca1e3e12483cc35c1cc341bccfcc3cca0978fb --- /dev/null +++ b/Src/Adaptive/new/UninitialisedMemoryProvider.hpp @@ -0,0 +1,135 @@ + +#ifndef UNINITIALISEDMEMORYPROVIDER_HPP +#define UNINITIALISEDMEMORYPROVIDER_HPP + +#include <cmath> +#include <memory> +#include <sstream> + +/** + * \brief Basic item provider + * + * A basic uninitialised memory provider. Provides heap allocation of a constant + * size array of objects. No reallocation/release can happen apart from deleting + * the provider. + * + * \tparam T Type of the object to provide memory for + */ +template<class T> +class UninitialisedMemoryProvider { +public: + using type = T; + +private: + using uninit_item = typename std::aligned_storage<sizeof(T)>::type; + const std::size_t _capacity = 1<<3; + std::size_t _size = 0; + std::unique_ptr<uninit_item[]> _data = + std::unique_ptr<uninit_item[]>{new uninit_item[this->_capacity]}; + + std::size_t& size() noexcept { + return this->_size; + } + + /** + * \brief Accessor to the underlying data + */ + type* data() noexcept { + return reinterpret_cast<type*>(this->_data.get()); + } + +public: + UninitialisedMemoryProvider() = delete; + UninitialisedMemoryProvider(const UninitialisedMemoryProvider&) = delete; + UninitialisedMemoryProvider& operator=(const UninitialisedMemoryProvider&) = delete; + UninitialisedMemoryProvider(UninitialisedMemoryProvider&&) = default; + UninitialisedMemoryProvider& operator=(UninitialisedMemoryProvider&&) = default; + + /** + * \brief Create a provider that may contain count items + * + * \param count Bucket capacity + */ + UninitialisedMemoryProvider(std::size_t count) : _capacity(count) {} + + /** + * \brief Total capacity of the provider + */ + std::size_t capacity() const noexcept { + return this->_capacity; + } + + /** + * \brief Already provided object count + */ + std::size_t size() const noexcept { + return this->_size; + } + + /** + * \brief Read-only accessor to the underlying data + */ + const type* data() const noexcept { + return reinterpret_cast<type*>(this->_data.get()); + } + + /** + * \brief Provide memory for given object count + * + * Checks that the provider can issue enough memory for the required object + * count and returns a pointer to the first one. + * + * \param count Required object count + * + * \return An array of uninitialised objects + * + * \exception std::length_error When the requested count cannot be provided + * (capacity - size < count) + */ + type* provide(const std::size_t count) { + if(! this->can_provide(count)) { + std::stringstream sstr; + sstr << "Cannot provide enough items, asked for "; + sstr << count << ", got " << this->capacity() - this->size(); + throw std::length_error(sstr.str()); + } + return unsafe_provide(count); + } + + + /** + * \brief Checks whether the provider has enought memory left or not + * + * \param count Object count + * + * \return True if at least memory for count items is available, false + * otherwise. + */ + bool can_provide(const std::size_t count) const noexcept { + return this->size() + count <= this->capacity(); + } + + /** + * \brief Provide memory for given object count without checks + * + * This version of the #provide method does not check nor throw in case or + * error, it is up to the user to ensure that enough memory is available + * before calling it. + * + * The provider size is updated accordinly to the request, therefore it is + * possible to check after the call is size <= capacity. + * + * \param count The required object count + * + * \return An array of uninitialised objects + */ + type* unsafe_provide(const std::size_t count) noexcept { + auto ret = this->data() + this->size(); + this->_size += count; + return ret; + } + +}; + + +#endif /* UNINITIALISEDMEMORYPROVIDER_HPP */