CD Code Digest Hub /GTA V

系统基础、容器、数学与内存体系

范围:Base system/atl/math/mathext/vector/vectormath/data/string,以及 paging 引用。静态分析,未运行。入口:Summary。

10 分钟阅读 11 个章节

1. 内存是架构的一部分#

RAGE 不将所有数据交给同一个默认堆。sysMemAllocator 统一分配/释放接口,具体实现包含 fixed/pool、buddy、best-fit、virtual、sparse、external、multi/split 等;资源和游戏对象又按用途进入不同 heap/memory bucket。

这让峰值、对齐、可移动性和平台 GPU 访问要求能够被显式控制。代价是“这个指针由谁分配、谁释放、能否移动、什么时候可以释放”成为每个对象的必要知识。

2. 分配器接口的细节#

接口包含 size、alignment、heap index,提供 Allocate 与可失败的 TryAllocate;SetQuitOnFail 能改变失败行为。Allocate 注释中“永不 NULL”与可配置失败策略应一起理解,不能只引用一句旧说明就断言调用者不需要考虑失败。

Resize 基类默认断言不支持,且参数语义是缩小;它不是所有分配器都支持的通用 realloc。AllocateMap/FreeMap 又让支持资源多块映射的 allocator 直接处理资源图。

current/master allocator、scope、memory bucket 与 user data 让临时切换分配目标及统计归属成为可能;移植时必须核对相关变量在目标宏下的 TLS/全局定义,而非凭 GetCurrent 名称假设线程局部。

3. 按生命周期选分配器#

数据类型合理机制及源码对应性能特点风险
大量同尺寸实例atl/pool、pool/fixed allocator分配快、对象定位便宜、容量可预测池耗尽、槽位重用、预留过大
单阶段临时数据task scratch、局部 arena 风格分配批量回收、少锁数据逃逸到下一阶段、最坏峰值越界
可装卸资源页buddy allocator分裂/合并、支持重定位和页粒度管理内部碎片、pin 阻挡合并
复杂长寿命对象general/simple/best-fit 等堆实现更灵活外部碎片、分配器争用和追踪难度
平台虚拟/稀疏资源virtual/sparse allocator 分支结合平台映射能力平台限制、提交与驻留语义不同

这张表表达用途选择,不声称每个模块都无条件使用对应实现;具体绑定要从 memmanager、游戏配置与对象分配入口确认。

4. 资源堆与游戏堆分离#

eMemoryType 明确包含 GAME/RESOURCE × VIRTUAL/PHYSICAL,以及 debug 分支。memory user data 又能标识 streaming index、draw pages、Scaleform、cloth、networking 等用途。

资源堆适合成块创建、按依赖卸载和搬移;游戏堆对象更常有长期指针和复杂析构。两者分离减少生命周期混杂,也允许针对内容资源做特殊恢复策略。

但只统计逻辑资源字节会低估真实内存:页对齐、allocator 元信息、空闲孔洞、指针跟踪、解压输入、CPU/GPU 双份以及搬移中的新旧拷贝都要计入。详见Streaming。

5. Buddy 与碎片整理#

sysMemBuddyAllocator 暴露 LargestAvailableBlock、Fragmentation、Defragment、LockBlock 等接口,说明 allocator 与资源系统共同维护可移动性。SparseBuddy 还有 deferred free,反映释放操作也可能需要延后到合法时机。

“总空闲足够”与“能满足指定大小和对齐”是两件事。多种大小且被长期 pin 的资源会留下无法利用的孔洞;临时大峰值也可能在最终驻留预算未满前触发失败。应同时记录总空闲、最大块、页浪费和不可移动占用。

新项目若选择虚拟地址稳定、物理页可重映射的方案,也仍须处理 GPU 同步、页表/映射开销和平台能力,不能以新 API 名称假定碎片问题消失。

6. pgRef 不是 shared_ptr#

pgRef 构造/赋值通过 known-reference 宏登记指针位置,资源装入时做 PointerFixup。析构只移除 known reference;显式 Release() 才调用 pointee 的 Release。复制构造也不是标准 shared_ptr 的自动 AddRef 语义。

这正是需要区分的两个问题:

  1. 引用位置追踪:对象移动时有哪些地址槽要改?
  2. 所有权/引用计数:谁让对象继续存活,何时销毁?

混淆它们会导致漏释放或过早释放。pgOwner、资源 constructor、streaming module 的 AddRef/RemoveRef 需要一起读,不能对所有 Ref 后缀类套用同一种 RAII 解释。

7. 容器与对象身份#

ATL 提供 array、fixed/range array、hash/map、pool、bitset 等,热点代码常用固定容量或预分配结构。atPool 让槽位索引化,iteratable pool 又支持遍历;是否具有代际校验取决于具体 pool/handle 类型,不能泛称所有池指针都能检测过期引用。

实体和资源 ID 是不同域;物理 level handle、streaming index、脚本对象引用的生命周期也不同。建议新项目强类型化 ID 并明确代际规则,在 release telemetry 中记录无效句柄来源,避免仅依靠调试断言。

紧凑 bitset 在物理 phase、可见性和状态掩码里尤为重要:它把随机对象判断变成按 word 的批量操作。代价是索引范围及稀疏高水位可能决定表大小;已删除大量对象但最大 index 仍高时,工作表未必同步缩小。

8. 数学、SIMD 与布局#

Vec3V/Vec4V/ScalarV/Mat34V 等向量类型贯穿物理和场景,传统 Vector/Matrix 与向量化 API 共存。*_In/_Out 参数约定、对齐宏、平台 intrinsic 和转置布局减少复制并让批处理适应硬件。

收益来自连续加载、并行算术和预取,而不只是“用了 SIMD”。实体 descriptor 的四对象块、BVH 量化节点、求解器缓存 collider 都是同一原则的具体化。分支多、对象随机、临时结果散落时,即使单个 dot product 向量化也难以改变整体瓶颈。

移植注意 alignment、padding、浮点舍入、NaN/denormal、endian、向量 lane 的隐含用途。代码里出现 w 存 flags 或额外数据的结构时,不应按普通三维向量操作随意覆盖它。

9. 平台内存一致性#

system 中的 interlocked、membarrier、cache 和 DMA 层服务不同硬件。某些平台内存说明明确区分 CPU cache 模式和 GPU 一致性路径;不能把“CPU 可以写、GPU 可以读”理解为完全自动一致。

任务完成、GPU fence、资源搬移结束与 allocator free 之间应有可追踪的 happens-before 关系。这里的缓存/屏障规则是算法正确性的一部分;把旧平台 DMA 调用删除但不重建所有权协议,可能留下难以复现的并发错误。

10. 面向新项目的建议与验证#

先定义四类预算:长期对象、可驻留资源、在途装载峰值和每帧临时内存。每类都要有高水位、失败行为和负责人;不要用一个“总内存还剩多少”数字管理全部系统。

对长跑和场景反复切换测量碎片与回收,不只看冷启动;对高密度角色、破碎和传送测瞬时峰值;对多 worker 测 allocator 锁争用和 false sharing。比较开发/发行配置时保留统计口径,debug heap 与标签开销可能改变行为。本次未运行这些实验。

11. 源码证据#

文件证据
memory.hmemory types/buckets;404 起 allocator;590–624 resource-map/current/master
memmanager.cpp内存管理装配与平台分支入口
buddyallocator.h可用块、碎片、block lock、defrag、deferred free
fixedallocator.hfixed-size 分配及 free-list
pool.hpool base、索引、iteratable pool
ref.h17–65 引用追踪与显式 Release
resource.h多块资源映射和指针重定位
EntityContainer.hSoA 与尺寸断言的实际例子

关联:资源管线、多线程、性能。