/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 Olive Team
This program 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 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
***/
#ifndef MEMORYPOOL_H
#define MEMORYPOOL_H
#include
#include
#include
#include
#include
#include
#include "common/define.h"
OLIVE_NAMESPACE_ENTER
template
/**
* @brief MemoryPool base class
*
* A custom memory system that allocates can allocate several objects in a large chunk (as opposed to several small
* allocations). Improves performance and memory consumption.
*
* As a class, this base is usable by setting the template to an object of your choosing. The pool will then allocate
* `(element_count * sizeof(T))` per arena. Arenas are allocated and destroyed on the fly - when an arena fills up,
* another is allocated.
*
* `Get()` will return an ElementPtr. The original desired data can be accessed through ElementPtr::data(). This data
* will belong to the caller until ElementPtr goes out of scope and the memory is freed back into the pool.
*/
class MemoryPool
{
public:
/**
* @brief Constructor
* @param element_count
*
* Number of elements per arena
*/
MemoryPool(int element_count) {
element_count_ = element_count;
ignore_arena_empty_signal_ = false;
}
/**
* @brief Destructor
*
* Deletes all arenas.
*/
virtual ~MemoryPool() {
Clear();
}
DISABLE_COPY_MOVE(MemoryPool)
/**
* @brief Clears all arenas, freeing all of their memory
*
* Note that this function is not safe, any elements that are still out there will be invalid
* and accessing them will cause a crash. You'll need to make sure all elements are already
* relinquished before then.
*/
void Clear()
{
ignore_arena_empty_signal_ = true;
qDeleteAll(arenas_);
ignore_arena_empty_signal_ = false;
}
/**
* @brief Returns whether any arenas are successfully allocated
*/
inline bool IsAllocated() const {
return !arenas_.isEmpty();
}
/**
* @brief Returns current number of allocated arenas
*/
inline int GetArenaCount() const {
return arenas_.size();
}
class Arena;
/**
* @brief A handle for a chunk of memory in an arena
*
* Calling Get() on the pool or arena will return a shared pointer to an element which will contain a pointer to
* the desired object/data in data(). When Element is destroyed (i.e. when ElementPtr goes out of scope), the memory
* is released back into the pool so it can be used by another class.
*/
class Element {
public:
/**
* @brief Element Constructor
*
* There is no need to use this outside of the memory pool's internal functions.
*/
Element(Arena* parent, T* data) {
parent_ = parent;
data_ = data;
accessed_ = QDateTime::currentMSecsSinceEpoch();
}
/**
* @brief Element Destructor
*
* Automatically releases this element's memory back to the arena it was retrieved from.
*/
~Element() {
release();
}
DISABLE_COPY_MOVE(Element)
/**
* @brief Access data represented in the pool
*/
inline T* data() const {
return data_;
}
inline const int64_t& timestamp() const {
return timestamp_;
}
inline void set_timestamp(const int64_t& timestamp) {
timestamp_ = timestamp;
}
/**
* @brief Register that this element has been accessed
*
* \see last_accessed()
*/
inline void access() {
accessed_ = QDateTime::currentMSecsSinceEpoch();
}
/**
* @brief Returns the last time `access()` was called on this function
*
* Useful for determining the relative age of an element (i.e. if it hasn't been accessed for a certain amount of
* time, it can probably be freed back into the pool). This requires all usages to call `access()`.
*/
inline const int64_t& last_accessed() const {
return accessed_;
}
void release() {
if (data_) {
parent_->Release(this);
data_ = nullptr;
}
}
private:
Arena* parent_;
T* data_;
int64_t timestamp_;
int64_t accessed_;
};
using ElementPtr = std::shared_ptr;
/**
* @brief A memory pool arena - a subsection of memory
*
* The pool itself does not store memory, it stores "arenas". This is so that the pool can handle the situation of
* an arena becoming full with no more memory to lend. A pool can automatically allocate another arena and continue
* providing memory (and freeing arenas when they're no longer in use).
*/
class Arena {
public:
Arena(MemoryPool* parent) {
parent_ = parent;
data_ = nullptr;
}
~Arena() {
std::list copy = lent_elements_;
foreach (Element* e, copy) {
e->release();
}
delete [] data_;
}
DISABLE_COPY_MOVE(Arena)
/**
* @brief Returns an element if there is free memory to do so
*/
ElementPtr Get() {
QMutexLocker locker(&lock_);
for (int i=0;i(this,
reinterpret_cast(data_ + i * element_sz_));
lent_elements_.push_back(e.get());
return e;
}
}
return nullptr;
}
/**
* @brief Releases an element back into the pool for use elsewhere
*/
void Release(Element* e) {
QMutexLocker locker(&lock_);
quintptr diff = reinterpret_cast(e->data()) - reinterpret_cast(data_);
int index = diff / element_sz_;
available_.replace(index, true);
lent_elements_.remove(e);
if (lent_elements_.empty()) {
locker.unlock();
parent_->ArenaIsEmpty(this);
}
}
int GetUsageCount() {
QMutexLocker locker(&lock_);
return lent_elements_.size();
}
bool Allocate(size_t ele_sz, size_t nb_elements) {
if (IsAllocated()) {
return true;
}
element_sz_ = ele_sz;
if ((data_ = new char[element_sz_ * nb_elements])) {
available_.resize(nb_elements);
available_.fill(true);
return true;
} else {
available_.clear();
return false;
}
}
inline int GetElementCount() const {
return available_.size();
}
inline bool IsAllocated() const {
return data_;
}
private:
MemoryPool* parent_;
char* data_;
QVector available_;
QMutex lock_;
size_t element_sz_;
std::list lent_elements_;
};
/**
* @brief Retrieves an element from an available arena
*/
ElementPtr Get() {
QMutexLocker locker(&lock_);
// Attempt to get an element from an arena
foreach (Arena* a, arenas_) {
ElementPtr e = a->Get();
if (e) {
return e;
}
}
// All arenas were empty, we'll need to create a new one
if (arenas_.empty()) {
qDebug() << "No arenas, creating new...";
} else {
qDebug() << "All arenas are full, creating new...";
}
size_t ele_sz = GetElementSize();
if (!ele_sz) {
qCritical() << "Failed to create arena, element size was 0";
return nullptr;
}
if (element_count_ <= 0) {
qCritical() << "Failed to create arena, element count was invalid:" << element_count_;
return nullptr;
}
Arena* a = new Arena(this);
if (!a->Allocate(ele_sz, element_count_)) {
qCritical() << "Failed to create arena, allocation failed. Out of memory?";
delete a;
return nullptr;
}
arenas_.push_back(a);
return a->Get();
}
void ArenaIsEmpty(Arena* a) {
// FIXME: Does this need to be mutexed?
if (ignore_arena_empty_signal_) {
return;
}
QMutexLocker locker(&lock_);
if (!a->GetUsageCount()) {
qDebug() << "Removing an empty arena";
arenas_.remove(a);
delete a;
}
}
protected:
/**
* @brief The size of each element
*
* Override this to use a custom size (e.g. a char array where T = char but the element size is > 1)
*/
virtual size_t GetElementSize() {
return sizeof(T);
}
private:
int element_count_;
std::list arenas_;
QMutex lock_;
bool ignore_arena_empty_signal_;
};
OLIVE_NAMESPACE_EXIT
#endif // MEMORYPOOL_H