kirikiri Z のメモリ周りの 設計の核心 をまとめた開発者向けドキュメント。
- per-allocator 容量ネゴ + テレメトリ (
iTVPMemoryAllocator) - TLSF プール (
TVPPooledAllocator) - 各 allocator (File / Bitmap / Sound)
- GlobalAllocStats (
operator newoverride + 経路振り分け) - システムアロケータ情報抽象 (
iTVPSystemAllocatorInfo) - AllocTagScope (thread-local tag stack)
- TJSObjectStats (Dict/Array tracking)
- ImageCache の構造 (key 正規化 / 自動解放 / pin)
ユーザ視点の使い方は doc/MemoryGuide.md。歴史的な検討記録 (案 A/B/C 比較、
段階導入計画 P1-P5 / I1-I7、撤退ライン等) は doc/legacy/ に退避してある。
[script]
│
[TJS_malloc / new]──→ TVPKrkrzMalloc ──┐
│ │
[SDL_*]──→ TVPGlobalAllocStats::SdlMalloc (KRKRZ_SDLMEMORY_STAT=ON)
│ │
▼ │ header (16B: size+magic+tag)
┌──────────────┐ │
│ TVPPooledAllocator │ (TLSF + system malloc fallback)
└──────────────┘
│
[FileAllocator / BitmapAllocator / SoundAllocator]
↑ iTVPMemoryAllocator (allocate/free + Stats + TagStats + capacity)
│ 個別 pool (TVPPooledAllocator) を内包
[script via StorageCache / Bitmap / Sound]
すべての allocator は iTVPMemoryAllocator を実装し、Application 経由で
差し替え可能 (組込みや LIB build 用)。GlobalAlloc は operator new を上書きする
ため interface ではなく直接 hook 形式。
win32/environ/Application.h / generic/environ/Application.h の共通インタフェース。
class iTVPMemoryAllocator {
public:
virtual void *allocate(size_t size) = 0;
virtual void *allocate(size_t size, TVPAllocTag tag) = 0;
virtual void free(void *p) = 0;
// realloc 相当 (libogg の _ogg_realloc 等のフック先)。
// デフォルトは getAllocatedSize() で旧サイズを取って allocate+memcpy+free。
// PS5 の mspace ベース実装は sceLibcMspaceRealloc で in-place 拡張を上書き。
virtual void *reallocate(void *old, size_t new_size, TVPAllocTag tag);
virtual size_t getAllocatedSize(void *p) const { return 0; } // 0 = 不明
// 容量ネゴ
virtual size_t capacity() const = 0; // SIZE_MAX = 上限なし
virtual size_t used() const = 0; // SIZE_MAX = 不明 (Unsized mode)
// テレメトリ
struct Stats {
size_t current_used = SIZE_MAX; // SIZE_MAX = 不明
size_t peak_used = SIZE_MAX;
uint64_t total_allocated = 0;
uint64_t total_freed = 0;
uint64_t alloc_count = 0;
uint64_t free_count = 0;
std::array<uint64_t, 8> alloc_size_hist = {}; // sizeBin
};
struct TagStats {
uint64_t alloc_count = 0;
uint64_t total_allocated = 0;
uint64_t free_count = 0;
size_t current_used = 0; // Sized + tag-aware free のみ
uint64_t total_freed = 0;
};
virtual Stats getStats() const = 0;
virtual TagStats getTagStats(TVPAllocTag tag) const = 0;
virtual void resetPeak() = 0;
};TVPAllocTag は alloc 元の用途ラベル:
Unknown / FileCache / BitmapBits / GraphicsLoader / Texture / Sound / Movie / TJS2 / User。
| # | 目標 |
|---|---|
| G1 | 容量と使用量を caller 側から問い合わせ可能 |
| G2 | 不明値は SIZE_MAX で素直に表現 |
| G3 | デフォルト実装 (raw malloc 経由) も同一 interface で動く |
| G4 | テレメトリは Stats / TagStats / sizeBin の 3 段 |
| G5 | tag は alloc 経路ではなく caller-side thread-local stack で振る |
8 段固定: <128 / <1K / <16K / <256K / <4M / <64M / <1G / >=1G。
分布の特徴 (微小 alloc が暴走している vs 巨大 alloc が散発)を一目で判定。
common/base/MemoryAllocatorStats.h の tTVPMemoryAllocatorStatsCollector
が atomic ベースで recordAlloc / recordFree / snapshot / resetPeak を提供。
KRKRZ_ENABLE_ALLOCATOR_STATS=OFF で全 record が no-op、snapshot は
SIZE_MAX を返す (不明マーカー)。Sized mode (BasicFileAllocator のような header 前置型)
と Unsized mode (size 不明) の 2 種。
common/base/PooledAllocator.{h,cpp}。
- 起動時に
VirtualAlloc(Win) /mmap(POSIX) で大きな連続領域を確保 - 内部 TLSF (Two-Level Segregated Fit) で O(1) alloc/free
- 容量を超えたら system
std::mallocに fallback (fallbackAllocCountで計数) - header に size + magic + tag (16 byte 揃え) を前置し、free 時の経路振り分けに使う
| caller | pool | デフォルト容量 (64-bit / 32-bit) |
|---|---|---|
| BasicFileAllocator | 専用 pool | -filepoolsize (256MB / 64MB) |
| tTVPBitmapBitsAlloc | 専用 pool | -bitmappoolsize (512MB / 128MB) |
| TVPSoundAllocator | 専用 pool | -soundpoolsize (16MB) |
| GlobalAlloc[Krkrz] | 専用 pool | -krkrzpoolsize (256MB / 64MB) |
| GlobalAlloc[SDL] | 専用 pool | -sdlpoolsize (64MB、KRKRZ_SDLMEMORY_STAT=ON 時のみ) |
各 pool は独立。pool に紐付けない設定 (-krkrzpoolsize=none) では
std::malloc 直行 + stats 計上のみ (header + magic を付けるので overhead 小)。
32-bit プロセスのデフォルト縮小: 32-bit はユーザアドレス空間が 2GB
(LAA + 64-bit OS 上で最大 4GB) しかなく、全 pool 合計 ~1GB を起動時に
先取りすると、汎用ヒープ (TJS / GL / 画像デコード / プラグイン) が細って
負荷時に OOM 即死する。そのため 32-bit では bitmap/file/krkrz の各デフォルトを
1/4 に落としてある (sizeof(void*) で分岐)。足りない案件は個別に
-bitmappoolsize=N 等で増やす。32-bit ビルドは /LARGEADDRESSAWARE を
必ず付ける (CMakeLists.txt、WIN32 かつ非 WIN64 かつ MSVC)。付けないと
2GB 制限のまま同じ枯渇を起こす (旧 32-bit ビルドは LAA 付きだった)。
pool 容量を超えると system std::malloc に fallback、fallbackAllocCount を
インクリメント、初回は WARNING ログを出す。fallback 経由のポインタも header magic
で識別 (kMagicRaw)、free 時に正しい経路を選ぶ。
allocate / free の pool 経路の入口で validateBlock() によりヘッダを
安価に検査する (範囲・16-align・payload 末尾が pool 内・FREE flag と free
リンクの整合)。破損を検知したら markCorrupted() で CRITICAL ログを出して
pool_corrupted_ を立て、以降は pool 経路を使わず fallback (system malloc)
へ縮退、pool 内ポインタの free はリークさせる (プロセスは落とさない)。
merge 相手 (prev/next 隣接ブロック) も merge 前に検査する。
この防御を入れた背景: メモリ枯渇の局面でヘッダが壊れたまま removeFree /
nextPhys に進むと 0 番地近傍への書き込み (即死 AV) になり、しかも
その AV が「例外メッセージをログ出力するための確保」の中で起きるため、
エラー表示もログも残さずプロセスが即死する (デバッガ無しでは原因不明の
「フリーズして落ちる」に見える)。縮退により、枯渇時でも fallback で確保が
続き、原因が CRITICAL ログに残る。
TVPPooledAllocator[Krkrz] cap=256MB block_size=64KB
TVPPooledAllocator[Bitmap] cap=512MB block_size=64KB
GlobalAllocStats: tracking activated (Krkrz pool=268435456 bytes, SDL pool=disabled)
common/base/FileAllocator.cpp。iTVPMemoryAllocator 実装 + TVPPooledAllocator
内包。tTJSBinaryStreamBuffer::create() (BinaryStreamBuffer.h) と
StorageCache から経由される。
Sized mode (alloc 時に size を header に書き込み、free 時に回収)。
win32/visual/BitmapBitsAlloc.cpp / generic/visual/BitmapBitsAlloc.cpp。
Bitmap pixel buffer の専用 allocator。BitmapPool (再利用) と組み合わせて
zero-fill 必須 (TLG decoder が pitch padding を埋めないため、
allocator 再利用で前画像残骸が出る; memory/feedback_bitmap_zero_fill.md 参照)。
Sized mode。Alloc 時に memset(p, 0, size) が必須。
common/base/SoundAllocator.{h,cpp}。tag = TVPAllocTag::Sound 専用。
Application::CreateSoundAllocator() がデフォルトでは TVPPooledAllocator
(TLSF、-soundpoolsize=N、既定 128MB) を返し、-soundpoolsize=none/off/0
のときだけ BasicSoundAllocator (raw malloc + 16B header) にフォールバック。
C 互換 API として sound_malloc / sound_calloc / sound_realloc / sound_free を
SoundAllocator.h から extern "C" でエクスポートする (KRKRZ_VARIANT=LIB も含め
全 build で利用可)。sound_realloc は iTVPMemoryAllocator::reallocate() を
呼び、デフォルト実装は getAllocatedSize() → allocate → memcpy → free で
内容コピーを保証する。BasicSoundAllocator / TVPPooledAllocator は
header / TLSF block から旧サイズを返せるので、libogg の _ogg_realloc 等の
フック先としてそのまま使える。
集計対象は:
- 既存の SoundSamples / RingBuffer / PhaseVocoder / WaveLoopManager
- AudioStream.cpp の
ma_engine本体 + miniaudio 内部 alloc (ma_engine_config.allocationCallbacksに sound_malloc/realloc/free を注入) - libogg / libvorbis 内部 (FetchContent ビルド +
-include sound_alloc_hook.h注入で_ogg_malloc系を sound_malloc に redirect; §4.4 参照) - libopus の decoder state (
OpusCodecDecoder.cppがopus_multistream_decoder_get_size+_initで sound_malloc 確保した buffer に init; §4.5 参照)
PS5 では PS5Application::CreateSoundAllocator() を override して
Direct Memory + sceLibcMspace("krkrz_sound", ...) 上の専用ヒープを構築
(ps5/src/app.cpp PS5SoundAllocator)。reallocate も override して
sceLibcMspaceRealloc で in-place 拡張を試みる。
krkrz/external/sound-codecs/CMakeLists.txt で xiph/ogg v1.3.6 と
xiph/vorbis v1.3.7 を FetchContent し、各ターゲットに
-include sound_alloc_hook.h(MSVC は/FI) で強制 includeexternal/sound-codecs/hooks/sound_alloc_hook.hが<ogg/os_types.h>を 読み込んでから_ogg_malloc / _ogg_calloc / _ogg_realloc / _ogg_freeを#undef+ 再定義し、sound_malloc / sound_calloc / sound_realloc / sound_freeに redirect する
を仕込む。アップストリームのソースには手を加えない。libvorbis は 200 箇所以上で
_ogg_* マクロを呼ぶが、すべて SoundAllocator 経由になる。
libvorbis 上流 CMakeLists 同梱の FindOgg.cmake (find_library で OGG_LIBRARY
を探す) は、OGG_LIBRARY / OGG_INCLUDE_DIR を CACHE に先置きすることで
find_library / find_path の検索を回避し、Ogg::ogg を ALIAS で先に提供して
IMPORTED target の重複生成を回避する。
libopus は vcpkg-installed バイナリをそのまま使う (allocator フックなし)。
代わりに common/sound/OpusCodecDecoder.cpp を opusfile 経由から libopus
直叩き に書き換え、opus_multistream_decoder_get_size(streams, coupled) で
返ったサイズを sound_malloc で確保した buffer に
opus_multistream_decoder_init() で in-place 初期化する。Ogg framing は
libogg の ogg_sync_* / ogg_stream_* API で自前実装。OpusHead / OpusTags は
RFC 7845 ベースで自前パース。シーク (granule_pos + pre_skip)・チェイン・
タグを含む opusfile 同等の機能をカバーする。
これにより:
- opusfile への依存撤去 (
vcpkg.jsonから削除) - libopus 自体は無改造のままで decoder state が SoundAllocator 配下になる
- libogg 内部 alloc (
_ogg_*経路) は §4.4 のフックで SoundAllocator 経由
common/utils/GlobalAllocStats.{h,cpp}。
- 検討した 3 案: A)
operator newoverride, B) mimalloc 全置換, C) OS スナップショット - 採用は 案 A: C++ 側のみだが、本体 exe 内の
new/TJS_malloc/ SDL3 alloc を 捕捉。プラグイン DLL 内 / C ライブラリ内部の素 malloc は依然対象外 (必要なら案 B mimalloc 全置換に進む)。案比較の詳細はdoc/legacy/GlobalAllocationStats.md§2 参照。
// pre-init (Initialize 未呼出)
new / TJS_malloc / SDL_malloc → std::malloc 直行 (header なし、stats なし、pool なし)
// post-init (Initialize 呼出後)
new / TJS_malloc / SDL_malloc → header (16B) 前置
→ pool に紐付いていれば pool 経由 (TLSF)
→ pool 枯渇 / pool 無効化なら std::malloc fallback
→ stats 計上 (tag = TVPCurrentAllocTag())+0: size_t size (8 byte)
+8: uint32_t magic (4 byte) — kMagicRaw / kMagicPool / それ以外は pre-init or 外部 alloc
+12: uint16_t tag (2 byte) — TVPAllocTag
+14: uint16_t pad (2 byte)
free 時は magic で経路振り分け、不一致 (pre-init / プラグイン由来等) は
素 std::free に流す。
TVPGetCommandLine が使える状態になった直後 (Application 組上げ後、
InitPath 完了後)。それより前 (SDL_Init / static initializer 等) の alloc は
pre-init モードで素 malloc を通る (= 観測対象外、コスト 0)。
詳細は memory/feedback_global_alloc_init_timing.md。
- size==0 でも non-null を返す必要あり (C++ 標準)。
newwrapper の先頭でsize = 1に丸める;memory/feedback_operator_new_size_zero.md参照 - pre-init で確保 → post-init で free のシーケンスは magic mismatch で
素
std::freeに流れる (一貫性あり)
common/utils/SystemAllocatorInfo.{h,cpp}。
iTVPMemoryAllocator が 本体管理下のプール (File / Bitmap / Sound /
GlobalAlloc) の情報を返すのに対し、iTVPSystemAllocatorInfo は その外側
- OS / プラットフォーム提供アロケータや、システム全体のメモリ状況 - を
抽象化する。コンソール機 (Switch 等) で
nn::os::GetTotalFreeSize()相当の プラットフォーム固有 API を露出させるためのフックポイントが主目的。
struct TVPSystemAllocatorStats {
size_t total_size = SIZE_MAX; // アロケータの総容量
size_t total_free_size = SIZE_MAX; // 空き領域合計 (GetTotalFreeSize 相当)
size_t allocatable_size = SIZE_MAX; // 確保可能な最大連続サイズ
size_t used_size = SIZE_MAX; // 使用中
size_t peak_used_size = SIZE_MAX;
// プロセスメモリ情報 (OS レベル)
size_t process_rss = SIZE_MAX;
size_t process_peak_rss = SIZE_MAX;
size_t process_vsize = SIZE_MAX;
// システム全体
size_t system_total_physical = SIZE_MAX;
size_t system_avail_physical = SIZE_MAX;
};
class iTVPSystemAllocatorInfo {
public:
// プラットフォームアロケータ互換 API
virtual size_t GetTotalFreeSize() const noexcept = 0;
virtual size_t GetAllocatableSize() const noexcept = 0;
virtual void Dump() const = 0;
// 一括取得 / 1 行サマリ
virtual TVPSystemAllocatorStats GetStats() const = 0;
virtual std::string GetSummary() const = 0;
};SIZE_MAX = 「情報が取得できない / 未対応」を示す sentinel。
一般 OS (Windows / macOS / Linux / Android) で、
- Win:
GlobalMemoryStatusExでシステム物理メモリ総量 / 空きを取得 - macOS:
sysctl(HW_MEMSIZE)で総量、host_statistics64で空きページ算出 - Linux:
sysinfo+/proc/meminfoのMemAvailableで空きを取得 process_*はTVPGetProcessMemoryInfo(RSS / peak / vsize) を流用
GetTotalFreeSize() / GetAllocatableSize() は一般 OS では malloc ヒープ
内の空きを正確に知ることができないため、システム空き物理メモリ
(Allocatable は保守的に ×80%) を近似値として返す。
tTVPApplication::GetSystemAllocatorInfo() は virtual。組込み向け派生
Application でオーバーライドして、そのプラットフォームの API を呼ぶ実装を
返す:
class tTVPApplicationSwitch : public tTVPApplication {
MySwitchSystemAllocatorInfo m_sysAlloc;
public:
iTVPSystemAllocatorInfo *GetSystemAllocatorInfo() override {
return &m_sysAlloc;
}
};TVPGetSystemAllocatorInfo() (free 関数) は Application 初期化前にも呼べる
よう、常にデフォルト実装の Meyers singleton を返す (MemoryOverlay の
sampler thread 等が用途)。
| 利用箇所 | 用途 |
|---|---|
TVPHeapDump (SystemImpl.cpp) |
各 allocator dump の後段にシステム情報を 1 セクション追加 |
REPL .mem / .sysalloc |
GetSummary() を 1 行表示 |
| MemoryOverlay (sampler) | sys_total_free / sys_allocatable を毎フレーム取得し SysFree 行に表示 |
TJS API System.getSystemAllocatorInfo() |
Dictionary で全 stats を返す |
common/utils/AllocTagScope.{h,cpp}。
{
TVPAllocTagScope _scope("TJS2"); // 以降の new / TJS_malloc は tag=TJS2
ExecuteSomeTJSCode();
} // RAII で pop- thread_local stack (深さ 16) で push/pop
TVPCurrentAllocTag()が stack top を返すKRKRZ_ENABLE_MEMSTAT_DETAIL=OFFのときは inline 空クラス (thread-local 参照しない)
| 場所 | tag |
|---|---|
common/tjs2/tjsScriptBlock.cpp ExecuteTopLevel |
TJS2 (起動時のみ) |
common/base/EventIntf.cpp TVPDeliverAllEvents |
TJS2 |
common/base/EventIntf.cpp TVPDeliverWindowUpdateEvents |
TJS2 |
common/base/EventIntf.cpp TVPDeliverContinuousEvent |
TJS2 |
common/base/EventIntf.cpp TVPDeliverCompactEvent |
TJS2 |
common/visual/GraphicsLoaderIntf.cpp TVPLoadGraphic |
GraphicsLoader |
script から System.beginAllocTag("...") / System.endAllocTag() |
User (任意名) |
common/tjs2/tjsObjectStats.{h,cpp}。KRKRZ_ENABLE_MEMSTAT_DETAIL=ON のみ動作。
| 追跡対象 | 方式 |
|---|---|
| tTJSCustomObject 総数 | std::atomic<uint64_t> (ctor/dtor で inc/dec) |
| tTJSDictionaryObject | std::unordered_set + std::mutex |
| tTJSArrayObject | std::unordered_set + std::mutex |
1. instance 総数
2. top-N (entries 数の多い順) の sample_keys 表示
3. entries 数別 bin (= 0 / 1-3 / 4-10 / 11-50 / 51-200 / >200) に分類
4. 各 bin 内で fingerprint (先頭 3 key を "|" 連結) の頻度集計、上位 3 件
bin + fingerprint で「小さい Dict が大量に増えてる」リーク (textlength|speechtext|text
パターンのメッセージバックログ等) が一目で見える。
script load
│
▼
TVPGraphicCache (image 層) — decoded Bitmap を保持、key=path
│ (cache miss)
▼
TVPLoadGraphic ──→ デコード
│
▼
StorageCache (file 層) — 生 byte 列を保持、key=path
│ (cache miss)
▼
file open
両層とも path を key にする。autopath 解決後の物理 path に正規化
(a50f37ac)。
Storages.pinCache(path):
- path をピン登録 (解放対象外)
- 未 cache なら自動 load (cache miss で decode し pin)
- 書き込み stream open 時に対応する pin/cache が evict される
pin set は 2 系統 で path 正規化 (a50f37ac):
- 入力 path 由来 (autopath 解決前)
- 物理 path 由来 (解決後)
両方で照合することで image/foo.png と data.xp3/image/foo.png が同じ pin を指す。
System.imageCacheLimit != 0 で ImageCache 有効化。デフォルトは
BitmapAllocator の pool capacity に揃える (例: 512 MB pool なら 512 MB)。
0 で無効化 (旧挙動: 毎回 decode し直し)。
cache table のエントリは「現存する buffer を指している」を invariant に維持
(59fa472d, 1e413695)。
- 通常 evict: table から外す + buffer release
- 外部 (TJS layer) が buffer を参照中: pinned 扱いで table に残す
- buffer がもう参照されていない: 即座に table から削除
これにより table を走査するだけで current_used が正しく取れる。
Storages.prefetchImage(path) / Storages.loadAsync(path):
- 別 thread で decode し ImageCache に投入
- 完了通知は callback (TJS) または
getFromCacheの結果 - file 層 (
StorageCache) も同様に auto-drop が漏れないよう、 prefetch / loadAsync 経路で明示 release を呼ぶ (f6d24b84,79ac82de)
| 構成要素 | 同期方式 |
|---|---|
| GlobalAllocStats counters | std::atomic (memory_order_relaxed) |
| GlobalAllocStats pool | TVPPooledAllocator (内部 mutex) |
| AllocatorStats collector | std::atomic (memory_order_relaxed) |
| Header magic check | atomic load (g_tracking_active) |
| TJSObjectStats sets | std::mutex |
| TJSObjectStats counters | std::atomic |
| AllocTagScope stack | thread_local (no sync) |
| MemoryOverlay sampler | 専用 thread + std::mutex |
| SystemAllocatorInfo | stateless (毎回 OS API 問い合わせ) |
| StorageCache / ImageCache | tTJSCriticalSection |
レース時、peak / ピーク値は瞬間値を取りこぼし得るが用途的に問題なし。
| option | OFF 時の効果 |
|---|---|
KRKRZ_ENABLE_ALLOC_STATS |
operator new override 消滅、TVPKrkrz* wrapper が素 malloc 直行、pool 構築せず |
KRKRZ_ENABLE_ALLOCATOR_STATS |
recordAlloc/Free no-op、snapshot は SIZE_MAX マーカー、used() は SIZE_MAX |
KRKRZ_ENABLE_MEMORY_OVERLAY |
sampler thread 起動せず、Render 関数空 stub、GL/SDL 描画コードは build される (LTCG で消える) |
KRKRZ_ENABLE_PERIODIC_DUMP |
TVPInitializeMemoryStatPeriodicDump 空、cmdline 解析されず |
KRKRZ_ENABLE_MEMSTAT_DETAIL |
size_hist / per-tag / TJSObjectStats が全削除、AllocTagScope は空クラス |
KRKRZ_SDLMEMORY_STAT |
SDL_SetMemoryFunctions を呼ばず、GlobalAlloc[SDL] collector 不使用 |
MASTER=ON ビルドで上記 6 つすべて FORCE OFF。
common/utils/GlobalAllocStats.{h,cpp}— operator new override / Krkrz poolcommon/utils/SystemAllocatorInfo.{h,cpp}— iTVPSystemAllocatorInfo + デフォルト実装common/utils/AllocTagScope.{h,cpp}— thread-local tag stackcommon/base/MemoryAllocatorStats.h— iTVPMemoryAllocator 用 stats collectorcommon/base/PooledAllocator.{h,cpp}— TLSF プールcommon/base/FileAllocator.cpp— BasicFileAllocatorwin32/visual/BitmapBitsAlloc.cpp/generic/visual/BitmapBitsAlloc.cpp— Bitmapcommon/base/SoundAllocator.{h,cpp}— Sound (-soundpoolsizeで TLSF pool 化、sound_malloc/calloc/realloc/free を extern "C" で公開)external/sound-codecs/CMakeLists.txt— libogg + libvorbis を FetchContent +-include sound_alloc_hook.hで_ogg_*を SoundAllocator に redirectexternal/sound-codecs/include/sound_alloc.h— C 互換 declarations (libogg/libvorbis 注入用)external/sound-codecs/hooks/sound_alloc_hook.h—<ogg/os_types.h>後付け overridecommon/sound/OpusCodecDecoder.cpp— libopus 直叩き (opusfile 撤去)、decoder state は sound_malloc 経由common/sound/AudioStream.cpp— miniaudio にma_allocation_callbacks = {sound_malloc, sound_realloc, sound_free}を注入ps5/src/app.cppPS5SoundAllocator— Direct Memory + sceLibcMspace 上の専用ヒープcommon/base/MemoryOverlay.{h,cpp}— sampler thread + snapshotsdl3/visual/MemoryOverlayRender.cpp— SDL_Renderer 描画common/visual/opengl/MemoryOverlayGL.cpp— OGL 直接描画common/base/MemoryStatPeriodicDump.{h,cpp}— 周期 dumpcommon/tjs2/tjsObjectStats.{h,cpp}— TJSObjectStatscommon/base/StorageCache.{h,cpp}— file 層 cachecommon/visual/GraphicsLoaderIntf.{h,cpp}— image 層 cache (TVPGraphicCache)
doc/MemoryGuide.md— ユーザ視点の使い方doc/DrawStats.md— 描画スレッドプール統計 (KRKRZ_DRAW_STATS)doc/legacy/— 設計検討の履歴GlobalAllocationStats.md— 案 A/B/C 比較、当初計画MemoryBudgetNegotiation.md— 容量ネゴ G1-G5 / テレメトリ T1-T4 / 段階導入 P1-P5MemoryInspection.md— 旧ユーザマニュアルMemoryLeakDiagnostic.md— 旧リーク調査ガイドImagePreloadAndCache.md— ImageCache 設計 / I1-I7 フェーズ / pinCache / cache key 統一の進捗記録