Fix #107704: Release infrequently used memory in Metal buffer pools #108083
|
@ -3,6 +3,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
#include <ctime>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
@ -231,17 +232,20 @@ class MTLCircularBuffer {
|
||||||
struct MTLBufferHandle {
|
struct MTLBufferHandle {
|
||||||
gpu::MTLBuffer *buffer;
|
gpu::MTLBuffer *buffer;
|
||||||
uint64_t buffer_size;
|
uint64_t buffer_size;
|
||||||
|
time_t insert_time;
|
||||||
|
|
||||||
inline MTLBufferHandle(gpu::MTLBuffer *buf)
|
inline MTLBufferHandle(gpu::MTLBuffer *buf)
|
||||||
{
|
{
|
||||||
this->buffer = buf;
|
this->buffer = buf;
|
||||||
this->buffer_size = this->buffer->get_size();
|
this->buffer_size = this->buffer->get_size();
|
||||||
|
this->insert_time = std::time(nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline MTLBufferHandle(uint64_t compare_size)
|
inline MTLBufferHandle(uint64_t compare_size)
|
||||||
{
|
{
|
||||||
this->buffer = nullptr;
|
this->buffer = nullptr;
|
||||||
this->buffer_size = compare_size;
|
this->buffer_size = compare_size;
|
||||||
|
this->insert_time = 0;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -354,7 +358,6 @@ class MTLBufferPool {
|
||||||
|
|
||||||
/* Debug statistics. */
|
/* Debug statistics. */
|
||||||
std::atomic<int> per_frame_allocation_count_;
|
std::atomic<int> per_frame_allocation_count_;
|
||||||
std::atomic<int64_t> allocations_in_pool_;
|
|
||||||
std::atomic<int64_t> buffers_in_pool_;
|
std::atomic<int64_t> buffers_in_pool_;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -399,6 +402,7 @@ class MTLBufferPool {
|
||||||
/* MTLBuffer::free() can be called from separate threads, due to usage within animation
|
/* MTLBuffer::free() can be called from separate threads, due to usage within animation
|
||||||
* system/worker threads. */
|
* system/worker threads. */
|
||||||
std::atomic<MTLSafeFreeList *> current_free_list_;
|
std::atomic<MTLSafeFreeList *> current_free_list_;
|
||||||
|
std::atomic<int64_t> allocations_in_pool_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void init(id<MTLDevice> device);
|
void init(id<MTLDevice> device);
|
||||||
|
|
|
@ -11,6 +11,12 @@
|
||||||
using namespace blender;
|
using namespace blender;
|
||||||
using namespace blender::gpu;
|
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 {
|
namespace blender::gpu {
|
||||||
|
|
||||||
/* -------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------- */
|
||||||
|
@ -28,9 +34,10 @@ void MTLBufferPool::init(id<MTLDevice> mtl_device)
|
||||||
/* Debug statistics. */
|
/* Debug statistics. */
|
||||||
total_allocation_bytes_ = 0;
|
total_allocation_bytes_ = 0;
|
||||||
per_frame_allocation_count_ = 0;
|
per_frame_allocation_count_ = 0;
|
||||||
allocations_in_pool_ = 0;
|
|
||||||
buffers_in_pool_ = 0;
|
buffers_in_pool_ = 0;
|
||||||
#endif
|
#endif
|
||||||
|
/* Track pool allocation size. */
|
||||||
|
allocations_in_pool_ = 0;
|
||||||
|
|
||||||
/* Free pools -- Create initial safe free pool */
|
/* Free pools -- Create initial safe free pool */
|
||||||
BLI_assert(current_free_list_ == nullptr);
|
BLI_assert(current_free_list_ == nullptr);
|
||||||
|
@ -159,11 +166,13 @@ gpu::MTLBuffer *MTLBufferPool::allocate_aligned(uint64_t size,
|
||||||
|
|
||||||
#if MTL_DEBUG_MEMORY_STATISTICS == 1
|
#if MTL_DEBUG_MEMORY_STATISTICS == 1
|
||||||
/* Debug. */
|
/* Debug. */
|
||||||
allocations_in_pool_ -= new_buffer->get_size();
|
|
||||||
buffers_in_pool_--;
|
buffers_in_pool_--;
|
||||||
BLI_assert(allocations_in_pool_ >= 0);
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/* Decrement size of pool. */
|
||||||
|
BLI_assert(allocations_in_pool_ >= 0);
|
||||||
|
allocations_in_pool_ -= new_buffer->get_size();
|
||||||
|
|
||||||
/* Ensure buffer memory is correctly backed. */
|
/* Ensure buffer memory is correctly backed. */
|
||||||
BLI_assert(new_buffer->get_metal_buffer());
|
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
|
#if MTL_DEBUG_MEMORY_STATISTICS == 1
|
||||||
printf("--- Allocation Stats ---\n");
|
printf("--- Allocation Stats ---\n");
|
||||||
printf(" Num buffers processed in pool (this frame): %u\n", num_buffers_added);
|
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);
|
std::multiset<MTLBufferHandle, CompareMTLBuffer> *pool = buffer_pools_.lookup(options);
|
||||||
pool->insert(MTLBufferHandle(buffer));
|
pool->insert(MTLBufferHandle(buffer));
|
||||||
|
allocations_in_pool_ += buffer->get_size();
|
||||||
|
|
||||||
#if MTL_DEBUG_MEMORY_STATISTICS == 1
|
#if MTL_DEBUG_MEMORY_STATISTICS == 1
|
||||||
/* Debug statistics. */
|
/* Debug statistics. */
|
||||||
allocations_in_pool_ += buffer->get_size();
|
|
||||||
buffers_in_pool_++;
|
buffers_in_pool_++;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Best for readability to add some constants for those large numbers.
MEMORY_SIZE_2GB = ....