From 5bc8fb4fdc4276796141e5600ab5895f1111ad85 Mon Sep 17 00:00:00 2001 From: Michael Parkin-White Date: Fri, 19 May 2023 15:11:02 +0100 Subject: [PATCH 1/2] Fix #107704: Release infrequently used memory in Metal buffer pools Excessive memory pool bloating could occur for certain workloads in Metal. Particularly those which continuously allocate increasingly large buffers with minimal re-use of existing buffers. New logic added to the memory pool flushes old buffers if they have not been used for a set period of time. Timing is calibrated against system resources and overall memory pressure. Metal memory pressure will run higher than OpenGL, however, this is an active decision to provide significant performance improvements for scenarios which allocate lots of memory and for keeping frames queued in flight without stalling on pending GPU work. Authored by Apple: Michael Parkin-White --- source/blender/gpu/metal/mtl_memory.hh | 6 ++- source/blender/gpu/metal/mtl_memory.mm | 64 ++++++++++++++++++++++++-- 2 files changed, 65 insertions(+), 5 deletions(-) diff --git a/source/blender/gpu/metal/mtl_memory.hh b/source/blender/gpu/metal/mtl_memory.hh index e7da1a6d4fd..0142f5d248a 100644 --- a/source/blender/gpu/metal/mtl_memory.hh +++ b/source/blender/gpu/metal/mtl_memory.hh @@ -3,6 +3,7 @@ #pragma once #include +#include #include #include #include @@ -231,17 +232,20 @@ class MTLCircularBuffer { struct MTLBufferHandle { gpu::MTLBuffer *buffer; uint64_t buffer_size; + time_t insert_time; inline MTLBufferHandle(gpu::MTLBuffer *buf) { this->buffer = buf; this->buffer_size = this->buffer->get_size(); + this->insert_time = std::time(nullptr); } inline MTLBufferHandle(uint64_t compare_size) { this->buffer = nullptr; this->buffer_size = compare_size; + this->insert_time = 0; } }; @@ -354,7 +358,6 @@ class MTLBufferPool { /* Debug statistics. */ std::atomic per_frame_allocation_count_; - std::atomic allocations_in_pool_; std::atomic buffers_in_pool_; #endif @@ -399,6 +402,7 @@ class MTLBufferPool { /* MTLBuffer::free() can be called from separate threads, due to usage within animation * system/worker threads. */ std::atomic current_free_list_; + std::atomic allocations_in_pool_; public: void init(id device); diff --git a/source/blender/gpu/metal/mtl_memory.mm b/source/blender/gpu/metal/mtl_memory.mm index 75a863da807..14e5fb02dba 100644 --- a/source/blender/gpu/metal/mtl_memory.mm +++ b/source/blender/gpu/metal/mtl_memory.mm @@ -28,9 +28,10 @@ void MTLBufferPool::init(id mtl_device) /* Debug statistics. */ total_allocation_bytes_ = 0; per_frame_allocation_count_ = 0; - allocations_in_pool_ = 0; buffers_in_pool_ = 0; #endif + /* Track pool allocation size. */ + allocations_in_pool_ = 0; /* Free pools -- Create initial safe free pool */ BLI_assert(current_free_list_ == nullptr); @@ -159,11 +160,13 @@ gpu::MTLBuffer *MTLBufferPool::allocate_aligned(uint64_t size, #if MTL_DEBUG_MEMORY_STATISTICS == 1 /* Debug. */ - allocations_in_pool_ -= new_buffer->get_size(); buffers_in_pool_--; - BLI_assert(allocations_in_pool_ >= 0); #endif + /* Decrement size of pool. */ + BLI_assert(allocations_in_pool_ >= 0); + allocations_in_pool_ -= new_buffer->get_size(); + /* Ensure buffer memory is correctly backed. */ BLI_assert(new_buffer->get_metal_buffer()); } @@ -275,6 +278,59 @@ void MTLBufferPool::update_memory_pools() } } + /* Release memory allocations which have not been used in a while. + * This ensures memory pressure stays low for scenes with compounding complexity during + * animation. + * If memory is continually used, then we do not want to free this memory as it will be + * re-allocated during a short time period. */ + const time_t time_now = std::time(nullptr); + for (auto buffer_pool_list : buffer_pools_.items()) { + MTLBufferPoolOrderedList *pool_allocations = buffer_pool_list.value; + MTLBufferPoolOrderedList::iterator pool_iterator = pool_allocations->begin(); + while (pool_iterator != pool_allocations->end()) { + + const MTLBufferHandle handle = *pool_iterator; + const time_t time_passed = time_now - handle.insert_time; + + /* Free allocations if a certain amount of time has passed. + * Deletion frequency depends on how much excess memory + * the application is using. */ + time_t deletion_time_threshold_s = 600; + /* Spare pool memory >= 2GB. */ + if (allocations_in_pool_ >= 2147483648LL) { + deletion_time_threshold_s = 2; + } + else + /* Spare pool memory >= 1GB. */ + if (allocations_in_pool_ >= 1073741824LL) + { + deletion_time_threshold_s = 4; + } + /* Spare pool memory >= 512MB.*/ + else if (allocations_in_pool_ >= 536870912LL) { + deletion_time_threshold_s = 15; + } + /* Spare pool memory >= 256MB. */ + else if (allocations_in_pool_ >= 268435456LL) { + deletion_time_threshold_s = 60; + } + + if (time_passed > deletion_time_threshold_s) { + + /* Delete allocation. */ + delete handle.buffer; + pool_iterator = pool_allocations->erase(pool_iterator); + allocations_in_pool_ -= handle.buffer_size; +#if MTL_DEBUG_MEMORY_STATISTICS == 1 + total_allocation_bytes_ -= handle.buffer_size; + buffers_in_pool_--; +#endif + continue; + } + pool_iterator++; + } + } + #if MTL_DEBUG_MEMORY_STATISTICS == 1 printf("--- Allocation Stats ---\n"); printf(" Num buffers processed in pool (this frame): %u\n", num_buffers_added); @@ -383,10 +439,10 @@ void MTLBufferPool::insert_buffer_into_pool(MTLResourceOptions options, gpu::MTL std::multiset *pool = buffer_pools_.lookup(options); pool->insert(MTLBufferHandle(buffer)); + allocations_in_pool_ += buffer->get_size(); #if MTL_DEBUG_MEMORY_STATISTICS == 1 /* Debug statistics. */ - allocations_in_pool_ += buffer->get_size(); buffers_in_pool_++; #endif } -- 2.30.2 From 7821c016c8660909f5f03b76871897cb26db28a7 Mon Sep 17 00:00:00 2001 From: Michael Parkin-White Date: Tue, 23 May 2023 11:48:20 +0100 Subject: [PATCH 2/2] Replace memory values with macros for readability --- source/blender/gpu/metal/mtl_memory.mm | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/source/blender/gpu/metal/mtl_memory.mm b/source/blender/gpu/metal/mtl_memory.mm index 14e5fb02dba..48170d3958c 100644 --- a/source/blender/gpu/metal/mtl_memory.mm +++ b/source/blender/gpu/metal/mtl_memory.mm @@ -11,6 +11,12 @@ using namespace blender; using namespace blender::gpu; +/* Memory size in bytes macros, used as pool flushing frequency thresholds. */ +#define MEMORY_SIZE_2GB 2147483648LL +#define MEMORY_SIZE_1GB 1073741824LL +#define MEMORY_SIZE_512MB 536870912LL +#define MEMORY_SIZE_256MB 268435456LL + namespace blender::gpu { /* -------------------------------------------------------------------- */ @@ -297,21 +303,21 @@ void MTLBufferPool::update_memory_pools() * the application is using. */ time_t deletion_time_threshold_s = 600; /* Spare pool memory >= 2GB. */ - if (allocations_in_pool_ >= 2147483648LL) { + if (allocations_in_pool_ >= MEMORY_SIZE_2GB) { deletion_time_threshold_s = 2; } else /* Spare pool memory >= 1GB. */ - if (allocations_in_pool_ >= 1073741824LL) + if (allocations_in_pool_ >= MEMORY_SIZE_1GB) { deletion_time_threshold_s = 4; } /* Spare pool memory >= 512MB.*/ - else if (allocations_in_pool_ >= 536870912LL) { + else if (allocations_in_pool_ >= MEMORY_SIZE_512MB) { deletion_time_threshold_s = 15; } /* Spare pool memory >= 256MB. */ - else if (allocations_in_pool_ >= 268435456LL) { + else if (allocations_in_pool_ >= MEMORY_SIZE_256MB) { deletion_time_threshold_s = 60; } -- 2.30.2