File: memorypool.h

package info (click to toggle)
olive-editor 20200620-2
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 40,228 kB
  • sloc: cpp: 51,932; sh: 56; makefile: 7; xml: 7
file content (371 lines) | stat: -rw-r--r-- 8,753 bytes parent folder | download
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