feat(ludic.physics): Jolt's heap counted in the shim (plan 25) - jph_heap_bytes/peak/allocs, phys_heap_bytes/peak/allocs
Jolt's Allocate/Reallocate/Free/AlignedAllocate/AlignedFree go to libc's malloc with the size in a 16-byte header, and atomic counters keep live bytes, the peak and the blocks asked for (its job threads allocate too). The fence reads the three exports extern_weak to judge Jolt by its plateau. jolt_heap_test: a ground in and out 1100 times holds 46,482,514 bytes after the first 100 and after all of them, the peak does not move, and a closed world gives back every byte it took. The macOS library is rebuilt; the Windows DLL still has to be rebuilt on the PC. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
7fdd6582be
commit
e9de2b6f27
5 changed files with 115 additions and 4 deletions
68
packages/ludic.physics/native/shim/jph_alloc.inl
Normal file
68
packages/ludic.physics/native/shim/jph_alloc.inl
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
// jph_alloc.inl - Jolt's allocations counted apart from the game's (plan 25): libc's malloc underneath,
|
||||
// each block carrying its size in a 16-byte header, so live bytes, the peak and the count are exact.
|
||||
// The fence reads them (extern_weak) to judge Jolt by its plateau, not per frame.
|
||||
#include <atomic>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
namespace {
|
||||
constexpr size_t kHead = 16;
|
||||
std::atomic<int64_t> g_live{0}, g_peak{0}, g_allocs{0};
|
||||
|
||||
void heap_add(int64_t n) {
|
||||
int64_t now = g_live.fetch_add(n, std::memory_order_relaxed) + n;
|
||||
int64_t p = g_peak.load(std::memory_order_relaxed);
|
||||
while (now > p && !g_peak.compare_exchange_weak(p, now, std::memory_order_relaxed)) {}
|
||||
}
|
||||
void *heap_alloc(size_t n) {
|
||||
char *b = static_cast<char *>(std::malloc(n + kHead));
|
||||
if (!b) return nullptr;
|
||||
std::memcpy(b, &n, sizeof n);
|
||||
g_allocs.fetch_add(1, std::memory_order_relaxed);
|
||||
heap_add(int64_t(n));
|
||||
return b + kHead;
|
||||
}
|
||||
void heap_free(void *p) {
|
||||
if (!p) return;
|
||||
char *b = static_cast<char *>(p) - kHead;
|
||||
size_t n;
|
||||
std::memcpy(&n, b, sizeof n);
|
||||
heap_add(-int64_t(n));
|
||||
std::free(b);
|
||||
}
|
||||
void *heap_realloc(void *p, size_t, size_t n) {
|
||||
if (!p) return heap_alloc(n);
|
||||
char *b = static_cast<char *>(p) - kHead;
|
||||
size_t old;
|
||||
std::memcpy(&old, b, sizeof old);
|
||||
char *q = static_cast<char *>(std::realloc(b, n + kHead));
|
||||
if (!q) return nullptr;
|
||||
std::memcpy(q, &n, sizeof n);
|
||||
heap_add(int64_t(n) - int64_t(old));
|
||||
return q + kHead;
|
||||
}
|
||||
// aligned: over-asked, the counted block's pointer kept in the word before the aligned one
|
||||
void *heap_aligned(size_t n, size_t align) {
|
||||
if (align < sizeof(void *)) align = sizeof(void *);
|
||||
char *raw = static_cast<char *>(heap_alloc(n + align + sizeof(void *)));
|
||||
if (!raw) return nullptr;
|
||||
uintptr_t at = (reinterpret_cast<uintptr_t>(raw) + sizeof(void *) + align - 1) & ~(uintptr_t(align) - 1);
|
||||
reinterpret_cast<void **>(at)[-1] = raw;
|
||||
return reinterpret_cast<void *>(at);
|
||||
}
|
||||
void heap_aligned_free(void *p) {
|
||||
if (p) heap_free(static_cast<void **>(p)[-1]);
|
||||
}
|
||||
void heap_register() {
|
||||
JPH::Allocate = heap_alloc;
|
||||
JPH::Reallocate = heap_realloc;
|
||||
JPH::Free = heap_free;
|
||||
JPH::AlignedAllocate = heap_aligned;
|
||||
JPH::AlignedFree = heap_aligned_free;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// bytes Jolt holds now, the most it ever held, and how many blocks it has asked for
|
||||
JPH_SHIM int64_t jph_heap_bytes(void) { return g_live.load(std::memory_order_relaxed); }
|
||||
JPH_SHIM int64_t jph_heap_peak(void) { return g_peak.load(std::memory_order_relaxed); }
|
||||
JPH_SHIM int64_t jph_heap_allocs(void) { return g_allocs.load(std::memory_order_relaxed); }
|
||||
|
|
@ -120,10 +120,12 @@ void *keep(const ShapeSettings::ShapeResult &r) {
|
|||
}
|
||||
} // namespace
|
||||
|
||||
// Jolt's allocator, factory and types, once per process; 0 when ready
|
||||
#include "jph_alloc.inl"
|
||||
|
||||
// Jolt's allocator (counted, jph_alloc.inl), factory and types, once per process; 0 when ready
|
||||
JPH_SHIM int jph_init(void) {
|
||||
if (g_ready) return 0;
|
||||
RegisterDefaultAllocator();
|
||||
heap_register();
|
||||
Factory::sInstance = new Factory();
|
||||
RegisterTypes();
|
||||
g_ready = true;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue