Fix #107704: Release infrequently used memory in Metal buffer pools #108083
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
/* 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
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Best for readability to add some constants for those large numbers.
MEMORY_SIZE_2GB = ....