Fix #107704: Release infrequently used memory in Metal buffer pools #108083

Merged
Jeroen Bakker merged 5 commits from Jason-Fielder/blender:MetalMemoryPool_Flushing into blender-v3.6-release 2023-06-01 15:48:36 +02:00
2 changed files with 71 additions and 5 deletions

View File

@ -3,6 +3,7 @@
#pragma once
#include <atomic>
#include <ctime>
#include <functional>
#include <map>
#include <mutex>
@ -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<int> per_frame_allocation_count_;
std::atomic<int64_t> allocations_in_pool_;
std::atomic<int64_t> 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<MTLSafeFreeList *> current_free_list_;
std::atomic<int64_t> allocations_in_pool_;
public:
void init(id<MTLDevice> device);

View File

@ -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 {
/* -------------------------------------------------------------------- */
@ -28,9 +34,10 @@ void MTLBufferPool::init(id<MTLDevice> 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 +166,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 +284,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;
Jeroen-Bakker marked this conversation as resolved
Review

Best for readability to add some constants for those large numbers.
MEMORY_SIZE_2GB = ....

Best for readability to add some constants for those large numbers. MEMORY_SIZE_2GB = ....
/* 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_ >= MEMORY_SIZE_2GB) {
deletion_time_threshold_s = 2;
}
else
/* Spare pool memory >= 1GB. */
if (allocations_in_pool_ >= MEMORY_SIZE_1GB)
{
deletion_time_threshold_s = 4;
}
/* Spare pool memory >= 512MB.*/
else if (allocations_in_pool_ >= MEMORY_SIZE_512MB) {
deletion_time_threshold_s = 15;
}
/* Spare pool memory >= 256MB. */
else if (allocations_in_pool_ >= MEMORY_SIZE_256MB) {
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 +445,10 @@ void MTLBufferPool::insert_buffer_into_pool(MTLResourceOptions options, gpu::MTL
std::multiset<MTLBufferHandle, CompareMTLBuffer> *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
}