125 lines
4.0 KiB
Markdown
125 lines
4.0 KiB
Markdown
|
|
# Allocator Selection And Current Limits
|
||
|
|
|
||
|
|
## 为什么引擎要有多种分配器
|
||
|
|
|
||
|
|
商业级引擎不会把所有内存都交给一套通用堆分配器。原因很现实:
|
||
|
|
|
||
|
|
- 帧级临时数据的生命周期极短,逐块释放反而是额外成本。
|
||
|
|
- 大量固定尺寸对象如果走通用堆,碎片和元数据开销通常不划算。
|
||
|
|
- 调试阶段又往往需要单独统计某个系统到底分了多少内存。
|
||
|
|
|
||
|
|
这就是 `XCEngine::Memory` 当前这组 API 的设计出发点。
|
||
|
|
|
||
|
|
## 现在这几个分配器分别适合什么
|
||
|
|
|
||
|
|
### `LinearAllocator`
|
||
|
|
|
||
|
|
适合:
|
||
|
|
|
||
|
|
- 一整批数据统一释放
|
||
|
|
- 帧级或阶段级临时内存
|
||
|
|
- 极低开销的顺序分配
|
||
|
|
|
||
|
|
不适合:
|
||
|
|
|
||
|
|
- 需要单个对象独立释放
|
||
|
|
- 需要通用 `realloc`
|
||
|
|
|
||
|
|
如果你熟悉 Unity 或其它引擎,可以把它理解为更接近 arena / frame allocator 这一类思路,而不是常驻对象堆。
|
||
|
|
|
||
|
|
### `PoolAllocator`
|
||
|
|
|
||
|
|
适合:
|
||
|
|
|
||
|
|
- 固定尺寸或尺寸上界稳定的对象
|
||
|
|
- 高频分配 / 释放
|
||
|
|
- 想避免通用堆碎片
|
||
|
|
|
||
|
|
不适合:
|
||
|
|
|
||
|
|
- 大小变化大的对象
|
||
|
|
- 需要可变尺寸重分配
|
||
|
|
|
||
|
|
### `ProxyAllocator`
|
||
|
|
|
||
|
|
适合:
|
||
|
|
|
||
|
|
- 给现有分配器加统计视图
|
||
|
|
- 调试某个系统的内存用量
|
||
|
|
- 不想修改底层分配器实现,但想额外记录指标
|
||
|
|
|
||
|
|
不适合:
|
||
|
|
|
||
|
|
- 被当作完整可信的内存分析器
|
||
|
|
|
||
|
|
## 当前版本最需要诚实面对的事
|
||
|
|
|
||
|
|
这套模块已经有了不错的 API 形状,但实现还没有完全跟上。
|
||
|
|
|
||
|
|
最重要的限制有这些:
|
||
|
|
|
||
|
|
- `LinearAllocator::Free` 和 `Reallocate` 还没有完成。
|
||
|
|
- `PoolAllocator::Reallocate` 还没有完成。
|
||
|
|
- `ProxyAllocator` 的释放统计当前不准确。
|
||
|
|
- `MemoryManager` 的泄漏和报表接口现在只是占位输出。
|
||
|
|
- 部分统计方法名字很强,但当前返回值更接近“固定上界”而不是真实历史峰值。
|
||
|
|
- 通过 `MemoryManager` 创建且依赖系统分配器的对象,目前仍要求调用方在 `Shutdown()` 之前主动销毁。
|
||
|
|
|
||
|
|
这意味着当前更合理的使用方式是:
|
||
|
|
|
||
|
|
- 把这些分配器当作明确生命周期的工具型组件;
|
||
|
|
- 不要把它们当成已经完备的内存诊断平台。
|
||
|
|
|
||
|
|
## 如何选择
|
||
|
|
|
||
|
|
如果你只是要一块批量临时内存:
|
||
|
|
|
||
|
|
```cpp
|
||
|
|
auto allocator = XCEngine::Memory::MemoryManager::Get().CreateLinearAllocator(1024 * 1024);
|
||
|
|
```
|
||
|
|
|
||
|
|
如果你要大量固定尺寸对象:
|
||
|
|
|
||
|
|
```cpp
|
||
|
|
auto allocator = XCEngine::Memory::MemoryManager::Get().CreatePoolAllocator(sizeof(MyNode), 1024);
|
||
|
|
```
|
||
|
|
|
||
|
|
如果你要统计某个系统的分配情况:
|
||
|
|
|
||
|
|
```cpp
|
||
|
|
auto allocator = XCEngine::Memory::MemoryManager::Get().CreateProxyAllocator("RenderGraph");
|
||
|
|
```
|
||
|
|
|
||
|
|
这里还要额外记住一点:
|
||
|
|
|
||
|
|
- `CreateProxyAllocator()` 应在 `Initialize()` 之后调用。
|
||
|
|
- `CreateLinearAllocator()` 如果绑定了系统分配器作为父分配器,也应在 `Shutdown()` 前完成销毁。
|
||
|
|
|
||
|
|
## 为什么要保留 `IAllocator`
|
||
|
|
|
||
|
|
`IAllocator` 的价值不在“虚函数本身”,而在于它允许上层容器和子系统表达“我需要一个分配策略”,而不是“我必须依赖某个具体实现”。
|
||
|
|
|
||
|
|
这和很多商业引擎里的思路一致:
|
||
|
|
|
||
|
|
- 容器依赖 allocator 接口
|
||
|
|
- 上层根据数据生命周期决定用哪种分配器
|
||
|
|
- 调试阶段可以额外套 tracking / proxy 层
|
||
|
|
|
||
|
|
## 当前阶段最值得补的方向
|
||
|
|
|
||
|
|
如果继续把这个模块做实,最值得优先补的通常是:
|
||
|
|
|
||
|
|
1. 给 `LinearAllocator` 和 `PoolAllocator` 明确补完 `Reallocate` / 回收语义,或者在 API 层彻底禁掉不支持的操作。
|
||
|
|
2. 修正 `ProxyAllocator` 的统计口径。
|
||
|
|
3. 让 `MemoryManager` 的泄漏与报表接口真正产出结构化结果。
|
||
|
|
4. 为不同分配器补充更严格的越界、重复释放和对齐校验。
|
||
|
|
|
||
|
|
## 相关 API
|
||
|
|
|
||
|
|
- [Memory](../../XCEngine/Memory/Memory.md)
|
||
|
|
- [IAllocator](../../XCEngine/Memory/Allocator/Allocator.md)
|
||
|
|
- [LinearAllocator](../../XCEngine/Memory/LinearAllocator/LinearAllocator.md)
|
||
|
|
- [PoolAllocator](../../XCEngine/Memory/PoolAllocator/PoolAllocator.md)
|
||
|
|
- [ProxyAllocator](../../XCEngine/Memory/ProxyAllocator/ProxyAllocator.md)
|
||
|
|
- [MemoryManager](../../XCEngine/Memory/MemoryManager/MemoryManager.md)
|