Attention une mise à jour du service Gitlab va être effectuée le mardi 30 novembre entre 17h30 et 18h00. Cette mise à jour va générer une interruption du service dont nous ne maîtrisons pas complètement la durée mais qui ne devrait pas excéder quelques minutes. Cette mise à jour intermédiaire en version 14.0.12 nous permettra de rapidement pouvoir mettre à votre disposition une version plus récente.

Commit d5ac184f authored by Quentin Khan's avatar Quentin Khan
Browse files

Adaptive tree: group memory allocation

parent bb75ec64
......@@ -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);
......
......@@ -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
......
#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 */
#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 */
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment