1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371
|
/***
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 <http://www.gnu.org/licenses/>.
***/
#ifndef MEMORYPOOL_H
#define MEMORYPOOL_H
#include <memory>
#include <QDateTime>
#include <QDebug>
#include <QLinkedList>
#include <QMutex>
#include <stdint.h>
#include "common/define.h"
OLIVE_NAMESPACE_ENTER
template <typename T>
/**
* @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() {
ignore_arena_empty_signal_ = true;
qDeleteAll(arenas_);
}
DISABLE_COPY_MOVE(MemoryPool)
/**
* @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<Element>;
/**
* @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<Element*> 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<available_.size();i++) {
if (available_.at(i)) {
// This buffer is available
available_.replace(i, false);
ElementPtr e = std::make_shared<Element>(this,
reinterpret_cast<T*>(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<quintptr>(e->data()) - reinterpret_cast<quintptr>(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<bool> available_;
QMutex lock_;
size_t element_sz_;
std::list<Element*> 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<Arena*> arenas_;
QMutex lock_;
bool ignore_arena_empty_signal_;
};
OLIVE_NAMESPACE_EXIT
#endif // MEMORYPOOL_H
|