Library: remove elaborate allocation logic
...due to the decision to use a much simpler allocation scheme to increase probability for actual savings, after switching the API and removing all trading related aspects, a lot of further code is obsoleted
This commit is contained in:
parent
13f51c910c
commit
eeda3aaa56
4 changed files with 9 additions and 394 deletions
|
|
@ -32,154 +32,13 @@
|
|||
#include "lib/allocation-cluster.hpp"
|
||||
#include "lib/error.hpp"
|
||||
#include "lib/util.hpp"
|
||||
#include "lib/sync.hpp"
|
||||
|
||||
#include <deque>
|
||||
|
||||
using util::isnil;
|
||||
|
||||
|
||||
namespace lib {
|
||||
|
||||
/**
|
||||
* "Low-level" Memory manager for allocating small objects of a fixed size.
|
||||
* The usage pattern is definite: Objects will be allocated in the course of
|
||||
* a build process and then live until all Objects will be purged in one sway.
|
||||
* Allocations will be requested one by one and immediately committed after
|
||||
* successful ctor call of the object being allocated. Allocations and commits
|
||||
* can be assumed to come in pairs, thus if an allocation immediately follows
|
||||
* another one (without commit), the previous allocation can be considered
|
||||
* a failure and can be dropped silently. After an allocation succeeds
|
||||
* (i.e. was committed), the MemoryManager is in charge for the lifecycle
|
||||
* of the object within the allocated space and has to guarantee calling
|
||||
* it's dtor, either on shutdown or on explicit #purge() -- the type info
|
||||
* structure handed in on initialisation provides a means for invoking
|
||||
* the dtor without actually knowing the object's type.
|
||||
*
|
||||
* @todo this is a preliminary or pseudo-implementation based on
|
||||
* a vector of raw pointers, i.e. actually the objects are heap
|
||||
* allocated. What actually should happen is for the MemoryManager to
|
||||
* allocate raw memory chunk wise, sub partition it and place the objects
|
||||
* into this private memory buffer. Further, possibly we could maintain
|
||||
* a pool of raw memory chunks used by all MemoryManager instances. I am
|
||||
* skipping those details for now (10/2008) because they should be based
|
||||
* on real-world measurements, not guessing.
|
||||
*/
|
||||
class AllocationCluster::MemoryManager
|
||||
: public Sync<RecursiveLock_NoWait>
|
||||
{
|
||||
typedef std::deque<char*> MemTable;
|
||||
TypeInfo type_;
|
||||
MemTable mem_;
|
||||
size_t top_; ///< index of the next slot available for allocation
|
||||
|
||||
public:
|
||||
MemoryManager(TypeInfo info) : top_(0) { reset(info); }
|
||||
~MemoryManager() { purge(); }
|
||||
|
||||
size_t size() const;
|
||||
|
||||
void purge();
|
||||
void reset(TypeInfo info);
|
||||
|
||||
void* allocate();
|
||||
|
||||
void commit (void* pendingAlloc);
|
||||
|
||||
private:
|
||||
void clearStorage();
|
||||
};
|
||||
|
||||
|
||||
|
||||
/** the top_ index always points at the next slot
|
||||
* not yet holding a finished, committed allocation.
|
||||
* Index is zero based, thus top_ == count of living objects
|
||||
*/
|
||||
size_t
|
||||
AllocationCluster::MemoryManager::size() const
|
||||
{
|
||||
return top_;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
AllocationCluster::MemoryManager::reset (TypeInfo info)
|
||||
{
|
||||
Lock sync{this};
|
||||
|
||||
if (0 < mem_.size()) purge();
|
||||
type_ = info;
|
||||
|
||||
ENSURE (0==top_);
|
||||
ENSURE (isnil (mem_));
|
||||
ENSURE (0 < type_.allocSize);
|
||||
ENSURE (type_.killIt);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
AllocationCluster::MemoryManager::purge()
|
||||
{
|
||||
Lock sync{this};
|
||||
|
||||
REQUIRE (type_.killIt, "we need a deleter function");
|
||||
REQUIRE (0 < type_.allocSize, "allocation size unknown");
|
||||
REQUIRE (top_ == mem_.size() || (top_+1) == mem_.size());
|
||||
|
||||
while (top_)
|
||||
type_.killIt (mem_[--top_]);
|
||||
clearStorage();
|
||||
}// note: unnecessary to kill pending allocations
|
||||
|
||||
|
||||
inline void
|
||||
AllocationCluster::MemoryManager::clearStorage()
|
||||
{
|
||||
for (size_t i=mem_.size(); 0 < i; )
|
||||
delete[] mem_[--i];
|
||||
mem_.clear();
|
||||
}
|
||||
|
||||
|
||||
inline void*
|
||||
AllocationCluster::MemoryManager::allocate()
|
||||
{
|
||||
Lock sync{this};
|
||||
|
||||
REQUIRE (0 < type_.allocSize);
|
||||
REQUIRE (top_ <= mem_.size());
|
||||
|
||||
if (top_==mem_.size())
|
||||
mem_.resize(top_+1);
|
||||
|
||||
if (!mem_[top_]) // re-use existing allocation, if any
|
||||
mem_[top_] = new char[type_.allocSize];
|
||||
|
||||
ENSURE (top_ < mem_.size());
|
||||
ENSURE (mem_[top_]);
|
||||
return mem_[top_];
|
||||
}
|
||||
|
||||
|
||||
inline void
|
||||
AllocationCluster::MemoryManager::commit (void* pendingAlloc)
|
||||
{
|
||||
Lock sync{this};
|
||||
|
||||
REQUIRE (pendingAlloc);
|
||||
ASSERT (top_ < mem_.size());
|
||||
ASSERT (pendingAlloc == mem_[top_], "allocation protocol violated");
|
||||
|
||||
++top_;
|
||||
|
||||
ENSURE (top_ == mem_.size());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** storage for static bookkeeping of type allocation slots */
|
||||
size_t AllocationCluster::maxTypeIDs;
|
||||
|
||||
|
||||
/** creating a new AllocationCluster prepares a table capable
|
||||
|
|
@ -199,99 +58,18 @@ namespace lib {
|
|||
AllocationCluster::~AllocationCluster() noexcept
|
||||
{
|
||||
try
|
||||
{ // avoiding a per-instance lock for now.
|
||||
ClassLock<AllocationCluster> guard; // (see note in the class description)
|
||||
{
|
||||
|
||||
TRACE (memory, "shutting down AllocationCluster");
|
||||
for (size_t i = typeHandlers_.size(); 0 < i; --i)
|
||||
if (handler(i))
|
||||
handler(i)->purge();
|
||||
|
||||
typeHandlers_.clear();
|
||||
|
||||
}
|
||||
catch (lumiera::Error & ex)
|
||||
{
|
||||
WARN (progress, "Exception while closing AllocationCluster: %s", ex.what());
|
||||
const char* errID = lumiera_error();
|
||||
TRACE (debugging, "Error flag was: %s", errID);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
ALERT (progress, "Unexpected fatal Exception while closing AllocationCluster.");
|
||||
lumiera::error::lumiera_unexpectedException(); // terminate
|
||||
}
|
||||
ERROR_LOG_AND_IGNORE (progress, "discarding AllocationCluster")
|
||||
}
|
||||
|
||||
|
||||
|
||||
void*
|
||||
AllocationCluster::initiateAlloc (size_t& slot)
|
||||
{
|
||||
if (!slot or slot > typeHandlers_.size() or !handler(slot) )
|
||||
return nullptr; // Memory manager not yet initialised
|
||||
else
|
||||
return handler(slot)->allocate();
|
||||
}
|
||||
|
||||
|
||||
void*
|
||||
AllocationCluster::initiateAlloc (TypeInfo type, size_t& slot)
|
||||
{
|
||||
ASSERT (0 < slot);
|
||||
|
||||
{ // avoiding a per-instance lock for now.
|
||||
ClassLock<AllocationCluster> guard; // (see note in the class description)
|
||||
|
||||
if (slot > typeHandlers_.size())
|
||||
typeHandlers_.resize(slot);
|
||||
if (not handler(slot))
|
||||
handler(slot).reset (new MemoryManager (type));
|
||||
|
||||
}
|
||||
|
||||
ASSERT (handler(slot));
|
||||
return initiateAlloc(slot);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
AllocationCluster::finishAlloc (size_t& slot, void* allocatedObj)
|
||||
{
|
||||
ASSERT (handler(slot));
|
||||
ASSERT (allocatedObj);
|
||||
|
||||
handler(slot)->commit(allocatedObj);
|
||||
}
|
||||
|
||||
|
||||
/* === diagnostics helpers === */
|
||||
|
||||
/** @return total number of objects
|
||||
* currently managed by this allocator */
|
||||
size_t
|
||||
AllocationCluster::size() const
|
||||
{
|
||||
size_t size(0);
|
||||
typedef ManagerTable::const_iterator Iter;
|
||||
|
||||
for (Iter ii= typeHandlers_.begin(); ii != typeHandlers_.end(); ++ii )
|
||||
if (*ii)
|
||||
size += (*ii)->size();
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
|
||||
size_t
|
||||
AllocationCluster::countActiveInstances (size_t& slot) const
|
||||
{
|
||||
if (handler (slot))
|
||||
return handler(slot)->size();
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
} // namespace lib
|
||||
|
|
|
|||
|
|
@ -122,11 +122,6 @@ namespace lib {
|
|||
|
||||
/* === diagnostics === */
|
||||
|
||||
size_t size() const;
|
||||
|
||||
template<class TY>
|
||||
size_t count() const;
|
||||
|
||||
size_t
|
||||
numExtents() const
|
||||
{
|
||||
|
|
@ -157,73 +152,6 @@ namespace lib {
|
|||
{
|
||||
return static_cast<X*> (allotMemory (cnt * sizeof(X)));
|
||||
}
|
||||
|
||||
|
||||
/** initiate an allocation for the given type */
|
||||
template<class TY>
|
||||
void*
|
||||
allocation();
|
||||
|
||||
/** finish the allocation after the ctor is successful */
|
||||
template<class TY>
|
||||
TY&
|
||||
commit (TY* obj);
|
||||
|
||||
|
||||
/**
|
||||
* The type-specific configuration information
|
||||
* any low-level memory manager needs to know
|
||||
*/
|
||||
struct TypeInfo;
|
||||
|
||||
/**
|
||||
* low-level memory manager responsible for
|
||||
* the allocations of one specific type.
|
||||
*/
|
||||
class MemoryManager;
|
||||
|
||||
/**
|
||||
* organising the association Type -> table entry
|
||||
*/
|
||||
template<class TY>
|
||||
struct TypeSlot;
|
||||
|
||||
static size_t maxTypeIDs;
|
||||
|
||||
|
||||
using HMemManager = ScopedPtrHolder<MemoryManager>;
|
||||
using Allo = Allocator_TransferNoncopyable<HMemManager>;
|
||||
using ManagerTable = std::vector<HMemManager,Allo>;
|
||||
|
||||
ManagerTable typeHandlers_; ///< table of active MemoryManager instances
|
||||
|
||||
|
||||
|
||||
HMemManager&
|
||||
handler (size_t slot)
|
||||
{
|
||||
ASSERT (0<slot && slot<=typeHandlers_.size());
|
||||
return typeHandlers_[slot-1];
|
||||
}
|
||||
|
||||
HMemManager const&
|
||||
handler (size_t slot) const
|
||||
{
|
||||
ASSERT (0<slot && slot<=typeHandlers_.size());
|
||||
return typeHandlers_[slot-1];
|
||||
}
|
||||
|
||||
/** implementation of the actual memory allocation
|
||||
* is pushed down to the MemoryManager impl. */
|
||||
void* initiateAlloc (size_t& slot);
|
||||
void* initiateAlloc (TypeInfo type, size_t& slot);
|
||||
|
||||
/** enrol the allocation after successful ctor call */
|
||||
void finishAlloc (size_t& slot, void*);
|
||||
|
||||
/** @internal helper for diagnostics,
|
||||
* delegating to actual memory manager */
|
||||
size_t countActiveInstances (size_t& slot) const;
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -232,97 +160,6 @@ namespace lib {
|
|||
|
||||
//-----implementation-details------------------------
|
||||
|
||||
struct AllocationCluster::TypeInfo
|
||||
{
|
||||
size_t allocSize;
|
||||
void (*killIt)(void*); ///< deleter function
|
||||
|
||||
template<class TY>
|
||||
TypeInfo(TY*)
|
||||
: allocSize(sizeof(TY)),
|
||||
killIt(&TypeSlot<TY>::kill)
|
||||
{ }
|
||||
|
||||
TypeInfo() ///< denotes "unknown" type
|
||||
: allocSize(0),
|
||||
killIt(0)
|
||||
{ }
|
||||
};
|
||||
|
||||
|
||||
|
||||
template<class TY>
|
||||
struct AllocationCluster::TypeSlot
|
||||
{
|
||||
static size_t id_; ///< table pos+1 of the memory manager in charge for type TY
|
||||
|
||||
static size_t &
|
||||
get()
|
||||
{
|
||||
return id_;
|
||||
}
|
||||
|
||||
static TypeInfo
|
||||
setup()
|
||||
{
|
||||
ClassLock<AllocationCluster> guard;
|
||||
if (0 == id_)
|
||||
id_= ++maxTypeIDs;
|
||||
|
||||
return TypeInfo ((TY*) 0 );
|
||||
}
|
||||
|
||||
static void
|
||||
kill (void* obj)
|
||||
{
|
||||
TY* p = static_cast<TY*>(obj);
|
||||
ASSERT (p);
|
||||
ASSERT (INSTANCEOF (TY,p));
|
||||
p->~TY();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
/** storage for static bookkeeping of type allocation slots */
|
||||
template<class TY>
|
||||
size_t AllocationCluster::TypeSlot<TY>::id_;
|
||||
|
||||
|
||||
|
||||
template<class TY>
|
||||
inline void*
|
||||
AllocationCluster::allocation()
|
||||
{
|
||||
void *mem = initiateAlloc (TypeSlot<TY>::get());
|
||||
if (not mem)
|
||||
mem = initiateAlloc (TypeSlot<TY>::setup(),TypeSlot<TY>::get());
|
||||
ENSURE (mem);
|
||||
return mem;
|
||||
}
|
||||
|
||||
template<class TY>
|
||||
inline TY&
|
||||
AllocationCluster::commit (TY* obj)
|
||||
{
|
||||
REQUIRE (obj);
|
||||
finishAlloc (TypeSlot<TY>::get(), obj);
|
||||
return *obj;
|
||||
}
|
||||
|
||||
|
||||
/** helper for diagnostics
|
||||
* @return number of currently allocated
|
||||
* object instances of the given type
|
||||
*/
|
||||
template<class TY>
|
||||
size_t
|
||||
AllocationCluster::count() const
|
||||
{
|
||||
return countActiveInstances (TypeSlot<TY>::get());
|
||||
}
|
||||
|
||||
|
||||
|
||||
} // namespace lib
|
||||
#endif /*LIB_ALLOCATION_CLUSTER_H*/
|
||||
|
|
|
|||
|
|
@ -457,7 +457,7 @@ namespace test{
|
|||
* all the elements might be added in one sway, by pulling them
|
||||
* from a Lumiera Forward Iterator. In case this is done in the
|
||||
* ctor, any exception while doing so will trigger cleanup
|
||||
* of all elements (and then failure of the ctor alltogether)
|
||||
* of all elements (and then failure of the ctor altogether)
|
||||
*/
|
||||
void
|
||||
verify_RAII_safety()
|
||||
|
|
@ -486,11 +486,9 @@ namespace test{
|
|||
elements.emplace<Num<3>> (4,5);
|
||||
elements.emplace<Num<6>> (7,8,9);
|
||||
|
||||
const size_t EXPECT = sizeof(Num<1>) + sizeof(Num<3>) + sizeof(Num<6>);
|
||||
CHECK (EXPECT == allocator.numBytes());
|
||||
CHECK (sum(9) == Dummy::checksum());
|
||||
CHECK (3 == allocator.size());
|
||||
CHECK (1 == allocator.count<Num<1>>());
|
||||
CHECK (1 == allocator.count<Num<3>>());
|
||||
CHECK (1 == allocator.count<Num<6>>());
|
||||
|
||||
CHECK (3 == elements.size());
|
||||
CHECK (1+2 == elements[2].getVal());
|
||||
|
|
@ -498,7 +496,7 @@ namespace test{
|
|||
CHECK (6+7+8+9 == elements[0].getVal());
|
||||
|
||||
elements.clear();
|
||||
CHECK (3 == allocator.size());
|
||||
CHECK (EXPECT == allocator.numBytes());
|
||||
CHECK (sum(9) == Dummy::checksum());
|
||||
// note: elements won't be discarded unless
|
||||
// the AllocationCluster goes out of scope
|
||||
|
|
|
|||
|
|
@ -81689,6 +81689,8 @@ Date:   Thu Apr 20 18:53:17 2023 +0200<br/>
|
|||
<node CREATED="1715782747520" ID="ID_1770179360" MODIFIED="1715782759542" TEXT="node-basic-test: OK"/>
|
||||
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1715782790810" ID="ID_1391251855" MODIFIED="1715782799529" TEXT="linked-elements-test: ">
|
||||
<icon BUILTIN="flag-yellow"/>
|
||||
<node CREATED="1715815439855" ID="ID_1456163487" MODIFIED="1715815486736" TEXT="sollte LinkedElements umstellen auf Standard-konformen Allocator"/>
|
||||
<node CREATED="1715815513228" ID="ID_1909349257" MODIFIED="1715815549523" TEXT="die NoOwnership-Policy wird dann zu einem speziellen Allocator"/>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1715782815501" ID="ID_845459708" MODIFIED="1715782818814" TEXT="Nodefactory">
|
||||
<icon BUILTIN="flag-yellow"/>
|
||||
|
|
@ -81941,8 +81943,8 @@ Date:   Thu Apr 20 18:53:17 2023 +0200<br/>
|
|||
<node CREATED="1680563460649" ID="ID_127710483" MODIFIED="1715624384997" TEXT="MemManagement">
|
||||
<arrowlink COLOR="#454059" DESTINATION="ID_1595450559" ENDARROW="Default" ENDINCLINATION="-1036;-77;" ID="Arrow_ID_1704085390" STARTARROW="None" STARTINCLINATION="-161;430;"/>
|
||||
<node CREATED="1715623566743" ID="ID_688601712" MODIFIED="1715624494222" TEXT="AllocationCluster">
|
||||
<linktarget COLOR="#816f7b" DESTINATION="ID_688601712" ENDARROW="Default" ENDINCLINATION="95;-321;" ID="Arrow_ID_859179840" SOURCE="ID_303611553" STARTARROW="None" STARTINCLINATION="-264;24;"/>
|
||||
<linktarget COLOR="#816f7b" DESTINATION="ID_688601712" ENDARROW="Default" ENDINCLINATION="95;-321;" ID="Arrow_ID_1440452458" SOURCE="ID_1219678116" STARTARROW="None" STARTINCLINATION="-521;38;"/>
|
||||
<linktarget COLOR="#816f7b" DESTINATION="ID_688601712" ENDARROW="Default" ENDINCLINATION="95;-321;" ID="Arrow_ID_859179840" SOURCE="ID_303611553" STARTARROW="None" STARTINCLINATION="-264;24;"/>
|
||||
<node CREATED="1715786334834" ID="ID_885525745" MODIFIED="1715786341860" TEXT="Design / Systematik">
|
||||
<node CREATED="1715786372102" ID="ID_1591071302" MODIFIED="1715786382355" TEXT="Aufwand für Memory-Management wird gebündelt"/>
|
||||
<node BACKGROUND_COLOR="#f8f1cb" COLOR="#a50125" CREATED="1715786522984" ID="ID_1870113951" MODIFIED="1715786580201" TEXT="Konflikt: Performance ⟷ Sicherheit">
|
||||
|
|
|
|||
Loading…
Reference in a new issue