BufferProvider: add simplistic heap-based BufferStore implementation
Now able to reap the rewards: since all that convoluted and tangled old ''state tracking'' has been resolved and replaced by dedicated and self-contained diagnostic subclasses, the old dummy BufferProvider implementation can be detached, using a similar yet very simple fake implementation. For context: the `HeapMemBufferStore` is part of the default configuration and thus automatically used whenever a unit test needs a `BufferProvider`. This default implementation is not optimised for speed, and actually never releases any memory, which allows to verify proper handling of buffer memory allocation.
This commit is contained in:
parent
2c8ee05a23
commit
7652ca05cd
5 changed files with 184 additions and 52 deletions
|
|
@ -237,7 +237,7 @@ namespace mem {
|
|||
virtual ~BufferStore() { } ///< this is an interface
|
||||
|
||||
virtual uint prepareBuffers (HashVal typeID, uint cnt,size_t) =0;
|
||||
virtual BuffAlloc provideBuffer (HashVal typeID, size_t,LocalTag,int64_t) =0;
|
||||
virtual BuffAlloc provideBuffer (HashVal typeID, size_t,LocalTag,int64_t customArg) =0;
|
||||
virtual void mark_emitted (HashVal typeID, BuffAlloc) =0;
|
||||
virtual void detachBuffer (HashVal typeID, BuffAlloc) =0;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -217,7 +217,6 @@ namespace mem {
|
|||
|
||||
DiagnosticBufferProvider::DiagnosticBufferProvider()
|
||||
: NaiveBufferSetup{}
|
||||
, heapStore_{dynamic_cast<HeapMemBufferStore&> (*bufferStore_)} //////////////////////////////////////////TICKET 1410 : obsolete after switch to newtracking-API
|
||||
, tracker_{std::make_unique<BlockTracker>()}
|
||||
{
|
||||
decorate<InstrumentedStageProxy> (bufferStage_, *tracker_, *this);
|
||||
|
|
@ -239,22 +238,6 @@ namespace mem {
|
|||
|
||||
/* === diagnostic API === */
|
||||
|
||||
bool
|
||||
BufferDiagnostic::buffer_was_used (uint bufferID)
|
||||
{
|
||||
NOTREACHED ("shall use new API only");
|
||||
// return dbp_.heapStore_.access_emitted(bufferID).was_used(); //////////////////////////////////////////TICKET 1410 : switch to newly defined tracking-API
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
BufferDiagnostic::buffer_was_closed (uint bufferID)
|
||||
{
|
||||
NOTREACHED ("shall use new API only");
|
||||
// return dbp_.heapStore_.access_emitted(bufferID).was_closed();//////////////////////////////////////////TICKET 1410 : switch to newly defined tracking-API
|
||||
}
|
||||
|
||||
|
||||
Buff*
|
||||
BufferDiagnostic::accessMemory (HashVal id)
|
||||
{ // may throw when found
|
||||
|
|
|
|||
|
|
@ -115,7 +115,6 @@ namespace mem {
|
|||
class DiagnosticBufferProvider
|
||||
: public NaiveBufferSetup
|
||||
{
|
||||
HeapMemBufferStore& heapStore_; //////////////////////////////OOO fällt dann weg nach dem Umbau
|
||||
class InstrumentedStageProxy;
|
||||
struct BlockTracker;
|
||||
std::unique_ptr<BlockTracker> tracker_;
|
||||
|
|
@ -138,8 +137,6 @@ namespace mem {
|
|||
public:
|
||||
BufferDiagnostic (DiagnosticBufferProvider&);
|
||||
|
||||
bool buffer_was_used (uint bufferID); ///< @deprecated ////////////////////////////////////////TICKET 1410
|
||||
bool buffer_was_closed (uint bufferID); ///< @deprecated ////////////////////////////////////////TICKET 1410
|
||||
bool was_created (HashVal);
|
||||
bool was_emitted (HashVal);
|
||||
bool was_released (HashVal);
|
||||
|
|
|
|||
|
|
@ -18,18 +18,27 @@
|
|||
|
||||
|
||||
#include "lib/error.hpp"
|
||||
#include "lib/integral.hpp"
|
||||
#include "include/logging.h"
|
||||
#include "lib/scoped-ptrvect.hpp"
|
||||
#include "vault/mem/buffer-metadata.hpp"
|
||||
#include "lib/format-string.hpp"
|
||||
#include "lib/format-obj.hpp"
|
||||
#include "lib/util-foreach.hpp"
|
||||
#include "lib/nocopy.hpp"
|
||||
|
||||
#include "vault/mem/heap-mem-buffer-store.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
|
||||
using util::and_all;
|
||||
using std::vector;
|
||||
using std::array;
|
||||
using lib::ScopedPtrVect;
|
||||
using util::contains;
|
||||
using util::_Fmt;
|
||||
|
||||
|
||||
|
||||
|
|
@ -261,73 +270,206 @@ namespace mem {
|
|||
const uint MAX_BUFFERS = 50;
|
||||
|
||||
Block emptyPlaceholder(0);
|
||||
|
||||
using StorageWord = uint64_t;
|
||||
constexpr inline size_t WORD_SIZ = sizeof(StorageWord);
|
||||
|
||||
constexpr size_t
|
||||
ceilDiv (size_t num, size_t den) noexcept
|
||||
{
|
||||
return (num + den - 1u) / den;
|
||||
}
|
||||
|
||||
constexpr size_t
|
||||
wordCnt (size_t sizeRequest) noexcept
|
||||
{
|
||||
return ceilDiv (sizeRequest, WORD_SIZ);
|
||||
}
|
||||
|
||||
static_assert (0 == wordCnt(0));
|
||||
static_assert (1 == wordCnt(1));
|
||||
static_assert (1 == wordCnt(WORD_SIZ));
|
||||
|
||||
inline void
|
||||
zeroFill (void* buf, size_t cnt)
|
||||
{
|
||||
auto begin = static_cast<StorageWord*> (buf);
|
||||
std::fill (begin, begin+cnt, StorageWord(0));
|
||||
}
|
||||
|
||||
} // (END) Details of allocation and accounting
|
||||
|
||||
|
||||
/**
|
||||
* @internal smart-handle and administrative record
|
||||
* to manage a single buffer allocation and verify
|
||||
* the proper sequence of lifecycle steps.
|
||||
* @remark since HeapMemBufferStore is the default for tests,
|
||||
* it seems prudent to perform additional sanity checks.
|
||||
*/
|
||||
class HeapMemBufferStore::Alloc
|
||||
: util::MoveOnly
|
||||
{
|
||||
Buff* mem_{nullptr};
|
||||
size_t siz_{0};
|
||||
BufferState state_{NIL};
|
||||
|
||||
void
|
||||
__requireState (BufferState expected)
|
||||
{
|
||||
if (state_ != expected)
|
||||
throw err::Logic{_Fmt{"HeapMemBufferStore: Lifecycle broken: "
|
||||
"expected_state:%d actual_state:%d"}
|
||||
% expected % state_
|
||||
,LERR_(LIFECYCLE)
|
||||
};
|
||||
}
|
||||
|
||||
void
|
||||
allocate (size_t cnt)
|
||||
{
|
||||
__requireState (NIL);
|
||||
REQUIRE (!mem_,"Attempt to re-allocate / double-allocate");
|
||||
REQUIRE (cnt, "Attempt to create a zero-sized buffer");
|
||||
siz_ = cnt;
|
||||
size_t words = wordCnt(cnt);
|
||||
mem_ = static_cast<Buff*> (std::aligned_alloc (std::alignment_of<StorageWord>(), words ));
|
||||
ENSURE (mem_);
|
||||
state_ = LOCKED;
|
||||
}
|
||||
|
||||
void
|
||||
discard()
|
||||
{
|
||||
if (mem_)
|
||||
std::free (mem_);
|
||||
mem_ = nullptr;
|
||||
siz_ = 0;
|
||||
state_ = BLOCKED;
|
||||
}
|
||||
|
||||
|
||||
Alloc (size_t size) { allocate(size); }
|
||||
|
||||
public:
|
||||
~Alloc() { discard(); }
|
||||
Alloc() = default;
|
||||
|
||||
Alloc (Alloc&& rr)
|
||||
: Alloc{}
|
||||
{
|
||||
std::swap (state_, rr.state_);
|
||||
std::swap (siz_, rr.siz_);
|
||||
std::swap (mem_, rr.mem_);
|
||||
}
|
||||
|
||||
|
||||
using IdxEntry = Index::value_type;
|
||||
|
||||
/**
|
||||
* Create new allocation and map a new entry for the
|
||||
* internal allocation index of HeapMemBufferStore.
|
||||
* @remark this is the only way to allocate.
|
||||
*/
|
||||
static Buff*
|
||||
makeNewAllocEntry (Index& idx, size_t buffSize)
|
||||
{
|
||||
Alloc newAlloc{buffSize};
|
||||
Buff* memLocation = newAlloc.mem_;
|
||||
if (contains (idx, memLocation))
|
||||
throw err::State{"HeapMemBufferStore allocated already "
|
||||
"registered memory address again."};
|
||||
idx.emplace (memLocation, move(newAlloc));
|
||||
ENSURE (contains (idx, memLocation));
|
||||
return memLocation;
|
||||
}
|
||||
|
||||
void
|
||||
verify (Buff* address, size_t size)
|
||||
{
|
||||
if (mem_ != address or siz_ != size)
|
||||
throw err::State{_Fmt{"HeapMemBufferStore allocation identity corrupted: "
|
||||
"Expect (size=%d|%s) yet invoked at state:%d with (%d|%s)."}
|
||||
% siz_ % util::showAdr(mem_) % state_ % size % util::showAdr(address)};
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
emit()
|
||||
{
|
||||
__requireState (LOCKED);
|
||||
state_ = EMITTED;
|
||||
}
|
||||
|
||||
void
|
||||
release()
|
||||
{
|
||||
if (state_ != LOCKED)
|
||||
__requireState (EMITTED);
|
||||
state_ = FREE;
|
||||
} // NOTE: Allocation retained (for diagnostics)
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @internal create a memory tracking BufferProvider,
|
||||
*/
|
||||
HeapMemBufferStore::HeapMemBufferStore()
|
||||
: pool_(new PoolTable)
|
||||
: allocIdx_{}
|
||||
, pool_(new PoolTable)
|
||||
, outSeq_()
|
||||
{ }
|
||||
|
||||
HeapMemBufferStore::~HeapMemBufferStore() { /* dtor of index table -> de-allocation here */ }
|
||||
|
||||
HeapMemBufferStore::~HeapMemBufferStore() { /* emit dtor of BlockPool here */ }
|
||||
|
||||
|
||||
/* ==== Implementation of the BufferProvider interface ==== */
|
||||
|
||||
uint
|
||||
HeapMemBufferStore::prepareBuffers (HashVal typeID, uint numBuffers, size_t buffSiz)
|
||||
HeapMemBufferStore::prepareBuffers (HashVal, uint numBuffers, size_t)
|
||||
{
|
||||
BlockPool& responsiblePool = getBlockPoolFor (buffSiz, typeID);
|
||||
return responsiblePool.prepare_for (numBuffers);
|
||||
return numBuffers;
|
||||
}
|
||||
|
||||
|
||||
BuffAlloc
|
||||
HeapMemBufferStore::provideBuffer (HashVal typeID, size_t buffSiz, LocalTag, int64_t)
|
||||
HeapMemBufferStore::provideBuffer (HashVal, size_t buffSiz, LocalTag specifics, int64_t)
|
||||
{
|
||||
BlockPool& blocks = getBlockPoolFor (buffSiz, typeID);
|
||||
Block& newBlock = blocks.createBlock();
|
||||
LocalTag specifics{&newBlock}; // used by this implementation to find the storage to release later
|
||||
return std::make_tuple (asBuffer(newBlock.accessMemory()), buffSiz, specifics);
|
||||
Buff* storage = Alloc::makeNewAllocEntry (allocIdx_, buffSiz);
|
||||
return std::make_tuple (storage, buffSiz, specifics);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
HeapMemBufferStore::mark_emitted (HashVal typeID, BuffAlloc storageSlot)
|
||||
HeapMemBufferStore::mark_emitted (HashVal, BuffAlloc storageSlot)
|
||||
{
|
||||
auto& [_,buffSiz,specifics] = storageSlot;
|
||||
Block* block4buffer = locateBlock (buffSiz, typeID, specifics);
|
||||
if (!block4buffer)
|
||||
throw err::Logic ("Attempt to emit a buffer not known to this BufferProvider"
|
||||
, LUMIERA_ERROR_BUFFER_MANAGEMENT);
|
||||
BlockPool& pool = getBlockPoolFor (buffSiz, typeID);
|
||||
Block* active = pool.transferResponsibility (block4buffer);
|
||||
if (active)
|
||||
outSeq_.manage (active);
|
||||
else
|
||||
if (block4buffer->was_closed())
|
||||
WARN (proc_mem, "Attempt to emit() an already closed buffer.");
|
||||
else
|
||||
WARN (proc_mem, "Attempt to emit() a buffer not found in active pool. "
|
||||
"Maybe duplicate call to emit()?");
|
||||
auto& [storage,buffSiz,specifics] = storageSlot;
|
||||
if (not contains (allocIdx_, storage))
|
||||
throw err::Invalid{_Fmt{"Passed buffer (size=%d|%s) for emit() call "
|
||||
"that is not recognised by this HeapMemBufferStore"}
|
||||
% buffSiz % util::showAdr(storage)};
|
||||
|
||||
auto& entry = allocIdx_[storage];
|
||||
entry.verify (storage,buffSiz);
|
||||
entry.emit();
|
||||
}
|
||||
|
||||
|
||||
/** mark a buffer as officially discarded */
|
||||
void
|
||||
HeapMemBufferStore::detachBuffer (HashVal typeID, BuffAlloc storageSlot)
|
||||
HeapMemBufferStore::detachBuffer (HashVal, BuffAlloc storageSlot)
|
||||
{
|
||||
auto& [storage,buffSiz,specifics] = storageSlot;
|
||||
Block* block4buffer = locateBlock (buffSiz, typeID, specifics);
|
||||
REQUIRE (block4buffer, "releasing a buffer not allocated through this provider");
|
||||
REQUIRE (util::isSameAdr (storage, block4buffer->accessMemory()));
|
||||
block4buffer->markReleased();
|
||||
if (not contains (allocIdx_, storage))
|
||||
throw err::Invalid{_Fmt{"Passed buffer (size=%d|%s) for release() call "
|
||||
"that is not recognised by this HeapMemBufferStore"}
|
||||
% buffSiz % util::showAdr(storage)};
|
||||
|
||||
auto& entry = allocIdx_[storage];
|
||||
entry.verify (storage,buffSiz);
|
||||
entry.release();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -35,6 +35,12 @@
|
|||
#include <memory>
|
||||
|
||||
|
||||
namespace std {
|
||||
/// Partial specializations for pointer types.
|
||||
template<typename _Tp>
|
||||
struct hash<_Tp*> : public hash_HIDDEN<_Tp*> { };
|
||||
|
||||
}
|
||||
namespace vault {
|
||||
namespace mem {
|
||||
|
||||
|
|
@ -55,6 +61,10 @@ namespace mem {
|
|||
class HeapMemBufferStore
|
||||
: public BufferProviderSetup::Store
|
||||
{
|
||||
class Alloc;
|
||||
using Index = std::unordered_map<Buff*,Alloc>;
|
||||
|
||||
Index allocIdx_;
|
||||
public: /////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1410 : looks like I'll re-implement the storage from scratch, since »tracking« is no longer required here
|
||||
class Block;
|
||||
class BlockPool;
|
||||
|
|
|
|||
Loading…
Reference in a new issue