Implementing the `EngineBufferManager` the desired way, with the ability to post allocation requests asynchronously, raises the difficult question of who will ultimately perform the allocation / deallocation and further management work. The point is that this management effort takes up capacity originally assigned for the render workers themselves, either by implicitly pushing aside those threads, or as a dedicated processing assigned to some worker. In the current stage of development it seems impossible to decide upon the proper mechanism to schedule this excess work explicitly. The solution proposed hereby however circumvents this dilemma, insofar it allows to implement the synchronous, fallback processing in terms of the asynchronous processing, just performed exclusively within the current thread. Unless some processing will be scheduled explicitly (which can be added as performance optimisation later on), the handling of a direct allocation request will ''fold down'' into the current worker eventually, thereby using it's processing capability without the need of any explicit scheduling.
243 lines
7 KiB
C++
243 lines
7 KiB
C++
/*
|
||
ENGINE-BUFFER-MANAGER.hpp - central buffer allocation coordinator
|
||
|
||
Copyright (C)
|
||
2026, Hermann Vosseler <Ichthyostega@web.de>
|
||
|
||
**Lumiera** is free software; you can redistribute it and/or modify it
|
||
under the terms of the GNU General Public License as published by the
|
||
Free Software Foundation; either version 2 of the License, or (at your
|
||
option) any later version. See the file COPYING for further details.
|
||
|
||
*/
|
||
|
||
/** @file engine-buffer-manager.hpp
|
||
** Central hub of buffer management within the Lumiera Render Engine.
|
||
** Instantiated as global service and part of the Engine, this global
|
||
** allocation coordinator is responsible to provide pre-allocated blocks
|
||
** for render buffers. These allocations are categorised by their size,
|
||
** and will be sent down to LocalMemPool instances running in each
|
||
** worker thread. This involves asynchronous messaging to avoid
|
||
** any blocking of work jobs due to central resource management.
|
||
**
|
||
** Besides the interface of the EngineBufferManager, this header also
|
||
** defines some common structures, like the \ref mem::Alloc, and the
|
||
** baseclass \ref AllocReceiver, with a lock-free inbound queue
|
||
** so that allocations can be supplied to a local memory manager
|
||
** and picked up from within the worker thread later.
|
||
** @remark both the LocalMemPool (for each worker thread) and the
|
||
** EngineBufferManager, as global memory service, are based on
|
||
** such an input queue, allowing to transfer control over some
|
||
** allocation from the central allocator to a local memory pool.
|
||
**
|
||
** @see EnginePufferManager_test
|
||
** @see LocalBufferStore_test
|
||
** @see BufferProviderProtocol_test
|
||
** @see buffer-provider.hpp
|
||
*/
|
||
|
||
#ifndef VAULT_MEM_ENGINE_BUFFR_MANAGA_H
|
||
#define VAULT_MEM_ENGINE_BUFFR_MANAGA_H
|
||
|
||
|
||
//#include "lib/hash-value.h"
|
||
#include "vault/mem/buffhandle.hpp"
|
||
//#include "vault/mem/type-handler.hpp"
|
||
//#include "vault/mem/buffer-local-tag.hpp"
|
||
#include "lib/nocopy.hpp"
|
||
//#include "lib/util.hpp"
|
||
|
||
#include <boost/lockfree/queue.hpp>
|
||
|
||
|
||
namespace vault{
|
||
namespace mem {
|
||
|
||
// using lib::HashVal;
|
||
// using util::unConst;
|
||
|
||
namespace err = lumiera::error;
|
||
|
||
|
||
|
||
namespace { // config and internal helpers...
|
||
|
||
const size_t INQUEUE_SIZ = 30; ///< initial size of the lock-free provision queue
|
||
|
||
}
|
||
|
||
struct Alloc
|
||
{
|
||
Buff* mem;
|
||
size_t siz;
|
||
|
||
bool empty() const { return not bool(mem); }
|
||
};
|
||
|
||
|
||
/**
|
||
* Base building block: a memory handler
|
||
* that can receive memory allocations for further use.
|
||
*/
|
||
class AllocReceiver
|
||
: util::NonCopyable
|
||
{
|
||
protected:
|
||
using InQueue = boost::lockfree::queue<Alloc>;
|
||
InQueue inQueue_;
|
||
|
||
AllocReceiver()
|
||
: inQueue_{INQUEUE_SIZ}
|
||
{ }
|
||
|
||
public:
|
||
/** Transfer control over the given allocation.
|
||
* @remark the \ref Alloc is placed into a lock-free queue;
|
||
* implementation code from a subclass will have to
|
||
* receive and dispatch those entries */
|
||
void
|
||
supply (Alloc const& alloc)
|
||
{
|
||
inQueue_.push (alloc);
|
||
}
|
||
};
|
||
|
||
|
||
|
||
|
||
/*********************************************************//**
|
||
* Core service of the Render Engine : global buffer manager.
|
||
*/
|
||
class EngineBufferManager
|
||
: public AllocReceiver
|
||
{
|
||
struct AllocRequest
|
||
{
|
||
AllocReceiver* receiver;
|
||
size_t sizRequest;
|
||
};
|
||
|
||
using RequestQueue = boost::lockfree::queue<AllocRequest>;
|
||
RequestQueue requestQueue_;
|
||
|
||
/// diagnostic bookkeeping
|
||
size_t allocated_{0};
|
||
size_t leased_{0};
|
||
size_t cnt_{0};
|
||
|
||
public:
|
||
EngineBufferManager()
|
||
: AllocReceiver{}
|
||
, requestQueue_{INQUEUE_SIZ}
|
||
{ }
|
||
|
||
|
||
size_t size() const;
|
||
bool empty() const;
|
||
|
||
|
||
/****************************************************************//**
|
||
* Core function: satisfy a request for allocation
|
||
* @return an \ref Alloc struct representing the desired allocation,
|
||
* can be empty to signal that the request can not be satisfied.
|
||
* @warning whoever accepts a non-empty result from this call
|
||
* is **responsible** to [return](\ref AllocReceiver::supply)
|
||
* this allocation eventually, to prevent memory leakage.
|
||
*/
|
||
Alloc requestAllocation (size_t sizRequest);
|
||
|
||
/**
|
||
* Standard access point: request an allocation
|
||
* that will be dispatched asynchronously to the given receiver.
|
||
* @remark without any temporal guarantees, this request will be
|
||
* processed eventually, placing an \ref Alloc into the
|
||
* [receiver's inqueue](\ref AllocReceiver::supply)
|
||
* @warning when the returned Alloc is [non-empty](\ref Alloc::empty),
|
||
* the receiver is responsible for returning it after use.
|
||
*/
|
||
void async_requestAllocation (AllocRequest const&);
|
||
|
||
/**
|
||
* Handle all enqueued asynchronous requests
|
||
* and perform pending management and allocation work.
|
||
* @remark this function can be invoked as a batch job;
|
||
* it acquires a lock and then handles the backlog.
|
||
*/
|
||
void processPendingRequests();
|
||
|
||
private:
|
||
/** process all pending requests in the queue */
|
||
void handleAllocRequests();
|
||
|
||
/** performs the actual allocation work */
|
||
Alloc doPerformAllocation (size_t sizRequest);
|
||
void doRedeemAllocation (Alloc);
|
||
|
||
/** a »backdoor« for unit testing */
|
||
friend class BufferManagerDiagnostic;
|
||
};
|
||
|
||
|
||
inline void
|
||
EngineBufferManager::async_requestAllocation (AllocRequest const& req)
|
||
{
|
||
|
||
}
|
||
|
||
inline void
|
||
EngineBufferManager::processPendingRequests()
|
||
{
|
||
inQueue_.consume_all ([this](Alloc alloc){ doRedeemAllocation (alloc); });
|
||
handleAllocRequests();
|
||
}
|
||
|
||
|
||
|
||
/** wrapper to inspect internals from a unit test */
|
||
class BufferManagerDiagnostic
|
||
: util::MoveOnly
|
||
{
|
||
EngineBufferManager const& globalPool_;
|
||
|
||
public:
|
||
BufferManagerDiagnostic (EngineBufferManager const& manager)
|
||
: globalPool_{manager}
|
||
{ }
|
||
|
||
bool
|
||
isEmpty()
|
||
{
|
||
return globalPool_.empty();
|
||
}
|
||
|
||
size_t
|
||
size()
|
||
{
|
||
return globalPool_.size();
|
||
}
|
||
|
||
size_t
|
||
bytesAllocd()
|
||
{
|
||
UNIMPLEMENTED ("alloc state diagnostic");
|
||
}
|
||
|
||
size_t
|
||
bytesLeased()
|
||
{
|
||
UNIMPLEMENTED ("alloc state diagnostic");
|
||
}
|
||
|
||
/////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1430 : add pool diagnostics here if we actually switch to pooled allocations
|
||
};
|
||
|
||
|
||
/** entrance point to inspection for test */
|
||
inline BufferManagerDiagnostic
|
||
watch (EngineBufferManager const& manager)
|
||
{
|
||
return BufferManagerDiagnostic{manager};
|
||
}
|
||
|
||
}} // namespace vault::mem
|
||
#endif /*VAULT_MEM_ENGINE_BUFFR_MANAGA_H*/
|