From 9d2ddbf727c6104183d617c6084e0bda04b29a84 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 30 Apr 2021 11:04:44 +1000 Subject: [PATCH] renderer/decoder: clean up decoders after some time --- app/codec/decoder.cpp | 20 ++++ app/codec/decoder.h | 9 ++ app/codec/ffmpeg/ffmpegframepool.h | 3 +- app/common/CMakeLists.txt | 7 +- app/common/memorypool.cpp | 14 --- app/common/memorypool.h | 158 +++++++++++++++++------------ app/render/rendermanager.cpp | 23 +++++ app/render/rendermanager.h | 7 ++ 8 files changed, 159 insertions(+), 82 deletions(-) delete mode 100644 app/common/memorypool.cpp diff --git a/app/codec/decoder.cpp b/app/codec/decoder.cpp index cf54907a5..e37f8649c 100644 --- a/app/codec/decoder.cpp +++ b/app/codec/decoder.cpp @@ -46,12 +46,15 @@ const rational Decoder::kAnyTimecode = RATIONAL_MIN; Decoder::Decoder() { + UpdateLastAccessed(); } bool Decoder::Open(const CodecStream &stream) { QMutexLocker locker(&mutex_); + UpdateLastAccessed(); + if (stream_.IsValid()) { // Decoder is already open. Return TRUE if the stream is the stream we have, or FALSE if not. if (stream_ == stream) { @@ -94,6 +97,8 @@ FramePtr Decoder::RetrieveVideo(const rational &timecode, const int ÷r) { QMutexLocker locker(&mutex_); + UpdateLastAccessed(); + if (!stream_.IsValid()) { qCritical() << "Can't retrieve video on a closed decoder"; return nullptr; @@ -111,6 +116,8 @@ SampleBufferPtr Decoder::RetrieveAudio(const TimeRange &range, const AudioParams { QMutexLocker locker(&mutex_); + UpdateLastAccessed(); + if (!stream_.IsValid()) { qCritical() << "Can't retrieve audio on a closed decoder"; return nullptr; @@ -167,10 +174,18 @@ SampleBufferPtr Decoder::RetrieveAudio(const TimeRange &range, const AudioParams return buffer; } +qint64 Decoder::GetLastAccessedTime() +{ + QMutexLocker locker(&mutex_); + return last_accessed_; +} + void Decoder::Close() { QMutexLocker locker(&mutex_); + UpdateLastAccessed(); + if (stream_.IsValid()) { CloseInternal(); stream_.Reset(); @@ -355,6 +370,11 @@ SampleBufferPtr Decoder::RetrieveAudioFromConform(const QString &conform_filenam return nullptr; } +void Decoder::UpdateLastAccessed() +{ + last_accessed_ = QDateTime::currentMSecsSinceEpoch(); +} + uint qHash(Decoder::CodecStream stream, uint seed) { return qHash(stream.filename(), seed) ^ qHash(stream.stream(), seed); diff --git a/app/codec/decoder.h b/app/codec/decoder.h index e4e47c327..7070b2ac0 100644 --- a/app/codec/decoder.h +++ b/app/codec/decoder.h @@ -165,6 +165,11 @@ public: */ SampleBufferPtr RetrieveAudio(const TimeRange& range, const AudioParams& params, const QString &cache_path, Footage::LoopMode loop_mode, const QAtomicInt *cancelled); + /** + * @brief Determine the last time this decoder instance was used in any way + */ + qint64 GetLastAccessedTime(); + /** * @brief Generate a Footage object from a file * @@ -276,12 +281,16 @@ signals: void IndexProgress(double); private: + void UpdateLastAccessed(); + SampleBufferPtr RetrieveAudioFromConform(const QString& conform_filename, const TimeRange &range, Footage::LoopMode loop_mode); CodecStream stream_; QMutex mutex_; + qint64 last_accessed_; + }; uint qHash(Decoder::CodecStream stream, uint seed = 0); diff --git a/app/codec/ffmpeg/ffmpegframepool.h b/app/codec/ffmpeg/ffmpegframepool.h index 6280d4db3..c3f9d050a 100644 --- a/app/codec/ffmpeg/ffmpegframepool.h +++ b/app/codec/ffmpeg/ffmpegframepool.h @@ -26,8 +26,9 @@ namespace olive { -class FFmpegFramePool : public MemoryPool +class FFmpegFramePool : public MemoryPool { + Q_OBJECT public: FFmpegFramePool(int element_count); diff --git a/app/common/CMakeLists.txt b/app/common/CMakeLists.txt index 2b9d42f8c..3524a50c2 100644 --- a/app/common/CMakeLists.txt +++ b/app/common/CMakeLists.txt @@ -37,23 +37,22 @@ set(OLIVE_SOURCES common/flipmodifiers.h common/functiontimer.h common/lerp.h - common/memorypool.cpp - common/otioutils.h common/memorypool.h common/ocioutils.cpp common/ocioutils.h common/oiioutils.cpp common/oiioutils.h + common/otioutils.h common/qtutils.cpp common/qtutils.h common/range.h common/ratiodialog.cpp common/ratiodialog.h - common/rational.h common/rational.cpp - common/threadsafemap.h + common/rational.h common/threadedobject.cpp common/threadedobject.h + common/threadsafemap.h common/timecodefunctions.cpp common/timecodefunctions.h common/timerange.cpp diff --git a/app/common/memorypool.cpp b/app/common/memorypool.cpp deleted file mode 100644 index 31e987e73..000000000 --- a/app/common/memorypool.cpp +++ /dev/null @@ -1,14 +0,0 @@ -#include "memorypool.h" - -namespace olive { - -size_t memory_pool_consumption = 0; -QMutex memory_pool_consumption_lock; - -bool MemoryPoolLimitReached() -{ - QMutexLocker locker(&memory_pool_consumption_lock); - return (memory_pool_consumption >= 2147483648); -} - -} diff --git a/app/common/memorypool.h b/app/common/memorypool.h index a4a5443f2..8435dee0b 100644 --- a/app/common/memorypool.h +++ b/app/common/memorypool.h @@ -22,21 +22,18 @@ #define MEMORYPOOL_H #include +#include #include #include #include #include +#include #include #include "common/define.h" namespace olive { -extern size_t memory_pool_consumption; -extern QMutex memory_pool_consumption_lock; -bool MemoryPoolLimitReached(); - -template /** * @brief MemoryPool base class * @@ -50,8 +47,9 @@ template * `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 +class MemoryPool : public QObject { + Q_OBJECT public: /** * @brief Constructor @@ -59,9 +57,15 @@ public: * * Number of elements per arena */ - MemoryPool(int element_count) { + MemoryPool(int element_count) + { element_count_ = element_count; - ignore_arena_empty_signal_ = false; + + clear_timer_ = new QTimer(); + clear_timer_->setInterval(kMaxEmptyArenaLife); + clear_timer_->moveToThread(qApp->thread()); + connect(clear_timer_, &QTimer::timeout, this, &MemoryPool::ClearEmptyArenas, Qt::DirectConnection); + QMetaObject::invokeMethod(clear_timer_, "start", Qt::QueuedConnection); } /** @@ -69,8 +73,10 @@ public: * * Deletes all arenas. */ - virtual ~MemoryPool() { + virtual ~MemoryPool() + { Clear(); + clear_timer_->deleteLater(); } DISABLE_COPY_MOVE(MemoryPool) @@ -84,22 +90,23 @@ public: */ void Clear() { - ignore_arena_empty_signal_ = true; qDeleteAll(arenas_); - ignore_arena_empty_signal_ = false; + arenas_.clear(); } /** * @brief Returns whether any arenas are successfully allocated */ - inline bool IsAllocated() const { - return !arenas_.isEmpty(); + inline bool IsAllocated() const + { + return !arenas_.empty(); } /** * @brief Returns current number of allocated arenas */ - inline int GetArenaCount() const { + inline int GetArenaCount() const + { return arenas_.size(); } @@ -119,7 +126,8 @@ public: * * There is no need to use this outside of the memory pool's internal functions. */ - Element(Arena* parent, T* data) { + Element(Arena* parent, uint8_t* data) + { parent_ = parent; data_ = data; accessed_ = QDateTime::currentMSecsSinceEpoch(); @@ -130,7 +138,8 @@ public: * * Automatically releases this element's memory back to the arena it was retrieved from. */ - ~Element() { + ~Element() + { release(); } @@ -139,15 +148,18 @@ public: /** * @brief Access data represented in the pool */ - inline T* data() const { + inline uint8_t* data() const + { return data_; } - inline const int64_t& timestamp() const { + inline const int64_t& timestamp() const + { return timestamp_; } - inline void set_timestamp(const int64_t& timestamp) { + inline void set_timestamp(const int64_t& timestamp) + { timestamp_ = timestamp; } @@ -156,7 +168,8 @@ public: * * \see last_accessed() */ - inline void access() { + inline void access() + { accessed_ = QDateTime::currentMSecsSinceEpoch(); } @@ -166,11 +179,13 @@ public: * 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 { + inline const int64_t& last_accessed() const + { return accessed_; } - void release() { + void release() + { if (data_) { parent_->Release(this); data_ = nullptr; @@ -180,7 +195,7 @@ public: private: Arena* parent_; - T* data_; + uint8_t* data_; int64_t timestamp_; @@ -199,23 +214,22 @@ public: */ class Arena { public: - Arena(MemoryPool* parent) { + Arena(MemoryPool* parent) + { parent_ = parent; data_ = nullptr; allocated_sz_ = 0; + empty_time_ = QDateTime::currentMSecsSinceEpoch(); } - ~Arena() { + ~Arena() + { std::list copy = lent_elements_; foreach (Element* e, copy) { e->release(); } delete [] data_; - - memory_pool_consumption_lock.lock(); - memory_pool_consumption -= allocated_sz_; - memory_pool_consumption_lock.unlock(); } DISABLE_COPY_MOVE(Arena) @@ -223,7 +237,8 @@ public: /** * @brief Returns an element if there is free memory to do so */ - ElementPtr Get() { + ElementPtr Get() + { QMutexLocker locker(&lock_); for (int i=0;i(this, - reinterpret_cast(data_ + i * element_sz_)); + reinterpret_cast(data_ + i * element_sz_)); lent_elements_.push_back(e.get()); return e; @@ -245,7 +260,8 @@ public: /** * @brief Releases an element back into the pool for use elsewhere */ - void Release(Element* e) { + void Release(Element* e) + { QMutexLocker locker(&lock_); quintptr diff = reinterpret_cast(e->data()) - reinterpret_cast(data_); @@ -256,17 +272,18 @@ public: lent_elements_.remove(e); if (lent_elements_.empty()) { - locker.unlock(); - parent_->ArenaIsEmpty(this); + empty_time_ = QDateTime::currentMSecsSinceEpoch(); } } - int GetUsageCount() { + int GetUsageCount() + { QMutexLocker locker(&lock_); return lent_elements_.size(); } - bool Allocate(size_t ele_sz, size_t nb_elements) { + bool Allocate(size_t ele_sz, size_t nb_elements) + { if (IsAllocated()) { return true; } @@ -275,14 +292,10 @@ public: allocated_sz_ = element_sz_ * nb_elements; - if ((data_ = new char[allocated_sz_])) { + if ((data_ = new uint8_t[allocated_sz_])) { available_.resize(nb_elements); available_.fill(true); - memory_pool_consumption_lock.lock(); - memory_pool_consumption += allocated_sz_; - memory_pool_consumption_lock.unlock(); - return true; } else { available_.clear(); @@ -291,18 +304,26 @@ public: } } - inline int GetElementCount() const { + inline int GetElementCount() const + { return available_.size(); } - inline bool IsAllocated() const { + inline bool IsAllocated() const + { return data_; } + inline qint64 GetTimeArenaWasMadeEmpty() + { + QMutexLocker locker(&lock_); + return empty_time_; + } + private: MemoryPool* parent_; - char* data_; + uint8_t* data_; size_t allocated_sz_; @@ -314,12 +335,15 @@ public: std::list lent_elements_; + qint64 empty_time_; + }; /** * @brief Retrieves an element from an available arena */ - ElementPtr Get() { + ElementPtr Get() + { QMutexLocker locker(&lock_); // Attempt to get an element from an arena @@ -361,29 +385,15 @@ public: 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); + virtual size_t GetElementSize() + { + return sizeof(uint8_t); } private: @@ -393,7 +403,29 @@ private: QMutex lock_; - bool ignore_arena_empty_signal_; + QTimer *clear_timer_; + + static const qint64 kMaxEmptyArenaLife = 5000; + +private slots: + void ClearEmptyArenas() + { + QMutexLocker locker(&lock_); + + const qint64 min_time = QDateTime::currentMSecsSinceEpoch() - kMaxEmptyArenaLife; + + for (auto it=arenas_.begin(); it!=arenas_.end(); ) { + Arena* arena = (*it); + + if (arena->GetUsageCount() == 0 && arena->GetTimeArenaWasMadeEmpty() <= min_time) { + qDebug() << "Removing an empty arena"; + delete arena; + it = arenas_.erase(it); + } else { + it++; + } + } + } }; diff --git a/app/render/rendermanager.cpp b/app/render/rendermanager.cpp index 1198145b4..4ebdf22a0 100644 --- a/app/render/rendermanager.cpp +++ b/app/render/rendermanager.cpp @@ -36,6 +36,7 @@ namespace olive { RenderManager* RenderManager::instance_ = nullptr; +const int RenderManager::kDecoderMaximumInactivity = 10000; RenderManager::RenderManager(QObject *parent) : ThreadPool(QThread::IdlePriority, 0, parent), @@ -56,6 +57,10 @@ RenderManager::RenderManager(QObject *parent) : decoder_cache_ = new DecoderCache(); shader_cache_ = new ShaderCache(); default_shader_ = context_->CreateNativeShader(ShaderCode(QString(), QString())); + + decoder_clear_timer_.setInterval(kDecoderMaximumInactivity); + connect(&decoder_clear_timer_, &QTimer::timeout, this, &RenderManager::ClearOldDecoders); + decoder_clear_timer_.start(); } else { qCritical() << "Tried to initialize unknown graphics backend"; context_ = nullptr; @@ -79,6 +84,24 @@ RenderManager::~RenderManager() } } +void RenderManager::ClearOldDecoders() +{ + QMutexLocker locker(decoder_cache_->mutex()); + + qint64 min_age = QDateTime::currentMSecsSinceEpoch() - kDecoderMaximumInactivity; + + for (auto it=decoder_cache_->begin(); it!=decoder_cache_->end(); ) { + DecoderPtr decoder = it.value(); + + if (decoder->GetLastAccessedTime() < min_age) { + decoder->Close(); + it = decoder_cache_->erase(it); + } else { + it++; + } + } +} + QByteArray RenderManager::Hash(const Node *n, const QString& output, const VideoParams ¶ms, const rational &time) { QCryptographicHash hasher(QCryptographicHash::Sha1); diff --git a/app/render/rendermanager.h b/app/render/rendermanager.h index d3c9d6362..d65d1a73a 100644 --- a/app/render/rendermanager.h +++ b/app/render/rendermanager.h @@ -144,6 +144,13 @@ private: QVariant default_shader_; + QTimer decoder_clear_timer_; + + static const int kDecoderMaximumInactivity; + +private slots: + void ClearOldDecoders(); + }; }