Files
Renderive/Core/base/Memory.cpp
T
2026-08-01 20:09:45 +08:00

212 lines
8.7 KiB
C++

#include "Memory.h"
#include <array>
#include <atomic>
#include <memory>
#include <mutex>
#include <new>
#include <stdexcept>
namespace renderive {
namespace {
constexpr std::size_t memory_domain_count = static_cast<std::size_t>(Memory_Domain::Other) + 1;
std::size_t memory_domain_index(Memory_Domain domain) noexcept {
std::size_t index = static_cast<std::size_t>(domain);
return index < memory_domain_count ? index : static_cast<std::size_t>(Memory_Domain::Other);
}
void* default_allocate(void*, std::size_t size, std::size_t alignment) {
return std::pmr::new_delete_resource()->allocate(size, alignment);
}
void default_deallocate(void*, void* address, std::size_t size, std::size_t alignment) {
std::pmr::new_delete_resource()->deallocate(address, size, alignment);
}
class Callback_Memory_Resource final : public std::pmr::memory_resource {
public:
Callback_Memory_Resource() : callbacks{nullptr, default_allocate, default_deallocate} {}
void set_callbacks(const Memory_Callbacks& value) {
callbacks = value.allocate ? value : Memory_Callbacks{nullptr, default_allocate, default_deallocate};
}
Memory_Stats stats() const noexcept {
Memory_Stats result;
result.upstream_allocation_count = allocation_count.load(std::memory_order_relaxed);
result.upstream_deallocation_count = deallocation_count.load(std::memory_order_relaxed);
result.upstream_current_bytes = current_bytes.load(std::memory_order_relaxed);
result.upstream_peak_bytes = peak_bytes.load(std::memory_order_relaxed);
return result;
}
private:
void* do_allocate(std::size_t size, std::size_t alignment) override {
void* result = callbacks.allocate(callbacks.context, size, alignment);
if (!result)
throw std::bad_alloc();
allocation_count.fetch_add(1, std::memory_order_relaxed);
std::uint64_t current = current_bytes.fetch_add(size, std::memory_order_relaxed) + size;
std::uint64_t peak = peak_bytes.load(std::memory_order_relaxed);
while (peak < current && !peak_bytes.compare_exchange_weak(peak, current, std::memory_order_relaxed)) {}
return result;
}
void do_deallocate(void* address, std::size_t size, std::size_t alignment) override {
callbacks.deallocate(callbacks.context, address, size, alignment);
deallocation_count.fetch_add(1, std::memory_order_relaxed);
current_bytes.fetch_sub(size, std::memory_order_relaxed);
}
bool do_is_equal(const std::pmr::memory_resource& other) const noexcept override {
return this == &other;
}
Memory_Callbacks callbacks;
std::atomic<std::uint64_t> allocation_count{};
std::atomic<std::uint64_t> deallocation_count{};
std::atomic<std::uint64_t> current_bytes{};
std::atomic<std::uint64_t> peak_bytes{};
};
class alignas(64) Domain_Memory_Resource final : public std::pmr::memory_resource {
public:
void set_domain(Memory_Domain value) noexcept {
domain = value;
}
Memory_Domain_Stats stats() const noexcept {
Memory_Domain_Stats result;
#if RENDERIVE_MEMORY_DOMAIN_STATS
result.allocation_count = allocation_count.load(std::memory_order_relaxed);
result.deallocation_count = deallocation_count.load(std::memory_order_relaxed);
result.current_bytes = current_bytes.load(std::memory_order_relaxed);
result.peak_bytes = peak_bytes.load(std::memory_order_relaxed);
#endif
return result;
}
private:
void* do_allocate(std::size_t size, std::size_t alignment) override;
void do_deallocate(void* address, std::size_t size, std::size_t alignment) override;
bool do_is_equal(const std::pmr::memory_resource& other) const noexcept override {
return this == &other;
}
Memory_Domain domain = Memory_Domain::Other;
#if RENDERIVE_MEMORY_DOMAIN_STATS
std::atomic<std::uint64_t> allocation_count{};
std::atomic<std::uint64_t> deallocation_count{};
std::atomic<std::uint64_t> current_bytes{};
std::atomic<std::uint64_t> peak_bytes{};
#endif
};
struct Memory_System {
Memory_System() {
for (std::size_t i = 0; i < domains.size(); ++i)
domains[i].set_domain(static_cast<Memory_Domain>(i));
}
std::mutex mutex;
Callback_Memory_Resource upstream;
std::unique_ptr<std::pmr::synchronized_pool_resource> small_pool;
std::atomic<std::pmr::memory_resource*> small_resource{};
std::atomic_bool initialized{};
bool configured{};
std::array<Domain_Memory_Resource, memory_domain_count> domains;
};
Memory_System& memory_system() {
static Memory_System value;
return value;
}
bool domain_uses_small_pool(Memory_Domain domain) {
switch (domain) {
case Memory_Domain::Renderable:
case Memory_Domain::Scheduler_Task:
case Memory_Domain::Update_Completion:
case Memory_Domain::Other:
return true;
default:
return false;
}
}
std::pmr::memory_resource* upstream_resource(Memory_System& system) {
system.initialized.store(true, std::memory_order_release);
return &system.upstream;
}
std::pmr::memory_resource* small_object_resource(Memory_System& system) {
system.initialized.store(true, std::memory_order_release);
#if RENDERIVE_MEMORY_GLOBAL_POOL
std::pmr::memory_resource* resource = system.small_resource.load(std::memory_order_acquire);
if (resource)
return resource;
std::lock_guard<std::mutex> lock(system.mutex);
resource = system.small_resource.load(std::memory_order_relaxed);
if (resource)
return resource;
std::pmr::pool_options options;
options.max_blocks_per_chunk = 64;
options.largest_required_pool_block = 4 * 1024;
system.small_pool = std::make_unique<std::pmr::synchronized_pool_resource>(options, &system.upstream);
resource = system.small_pool.get();
system.small_resource.store(resource, std::memory_order_release);
return resource;
#else
return &system.upstream;
#endif
}
std::pmr::memory_resource* domain_resource(Memory_Domain domain) {
Memory_System& system = memory_system();
if (domain_uses_small_pool(domain))
return small_object_resource(system);
return upstream_resource(system);
}
void* Domain_Memory_Resource::do_allocate(std::size_t size, std::size_t alignment) {
void* result = domain_resource(domain)->allocate(size, alignment);
#if RENDERIVE_MEMORY_DOMAIN_STATS
allocation_count.fetch_add(1, std::memory_order_relaxed);
std::uint64_t current = current_bytes.fetch_add(size, std::memory_order_relaxed) + size;
std::uint64_t peak = peak_bytes.load(std::memory_order_relaxed);
while (peak < current && !peak_bytes.compare_exchange_weak(peak, current, std::memory_order_relaxed)) {}
#endif
return result;
}
void Domain_Memory_Resource::do_deallocate(void* address, std::size_t size, std::size_t alignment) {
domain_resource(domain)->deallocate(address, size, alignment);
#if RENDERIVE_MEMORY_DOMAIN_STATS
deallocation_count.fetch_add(1, std::memory_order_relaxed);
current_bytes.fetch_sub(size, std::memory_order_relaxed);
#endif
}
}
void set_memory_callbacks(const Memory_Callbacks& callbacks) {
if (!!callbacks.allocate != !!callbacks.deallocate)
throw std::invalid_argument("allocate and deallocate must be configured together");
Memory_System& system = memory_system();
std::lock_guard<std::mutex> lock(system.mutex);
if (system.configured || system.initialized.load(std::memory_order_acquire))
throw std::logic_error("Core memory resource is already configured");
system.upstream.set_callbacks(callbacks);
system.configured = true;
}
Memory_Stats memory_stats() noexcept {
return memory_system().upstream.stats();
}
Memory_Domain_Stats memory_domain_stats(Memory_Domain domain) noexcept {
return memory_system().domains[memory_domain_index(domain)].stats();
}
std::pmr::memory_resource* memory_resource() {
return small_object_resource(memory_system());
}
std::pmr::memory_resource* memory_resource(Memory_Domain domain) {
#if RENDERIVE_MEMORY_DOMAIN_STATS
Memory_System& system = memory_system();
system.initialized.store(true, std::memory_order_release);
return &system.domains[memory_domain_index(domain)];
#else
return domain_resource(domain);
#endif
}
void release_unused_memory() {
Memory_System& system = memory_system();
std::lock_guard<std::mutex> lock(system.mutex);
if (system.small_pool)
system.small_pool->release();
}
void shutdown_memory() {
Memory_System& system = memory_system();
std::lock_guard<std::mutex> lock(system.mutex);
system.small_resource.store(nullptr, std::memory_order_release);
system.small_pool.reset();
system.initialized.store(false, std::memory_order_release);
system.configured = false;
system.upstream.set_callbacks(Memory_Callbacks{});
}
}