上一篇我们实现了一个简单的基于块链的Allocator。
接下来我们来实现我们的内存管理模块:Memory Manager
根据之前我们的讨论,我们设计Memory Manager为这样一个角色,它总管着所有动态分配的内存。(但是严格来说,诸如堆栈,自动变量,Memory Manager创建之前创建的对象,以及一些全局对象,比如代表我们架构里的各种模块,也就是***Manager,并不由其管理)
这种管理,是通过管理一系列的Allocator来实现的。每种Allocator,代表了一种分配策略。Allocator以页(Page)为单位获取资源,再以块(Block)为单位分配资源。
采用这种结构的最大好处是:我们可以很方便地添加新类型的Allocator,并通过修改内存分配需求(Request)与分配器(Allocator)之间的映射关系(Allocator Lookup Policy)来快速地实现新的内存分配策略。
另外的好处是:我们可以通过一个线程与Allocator之间的绑定关系,迅速地实现线程的本地堆(Thread Local Storage)。这个堆由于为某个线程所独占,所以并不需要互锁机制,从而可以大大地加速线程的执行速度。
而且,这种结构还可以纵向拓展。如参考引用2那样,只要稍加改造,我们可以在Allocator之间形成层级关系以及兄弟(slibing)关系。
这种层级关系的意义在于,如果我们将一个很复杂的处理划分为一些单纯的短片段的话,那么每个片段的内存访问模式(access pattern)是有规律可循的。也就是说,有的片段总是倾向于频繁的小块内存使用;有的则是大块大块的使用;有的不怎么使用;有的则突发性大量使用,等等。这些不同的使用频率和使用强度,如果我们在同一个层级对其进行管理,那么状况就十分复杂,变得不确定性很强,很难预测;然而如果我们能归纳它们的特征,尽量将类似频率和强度的大量处理组织在同一个层级,那么同一个层级的互相随机叠加,此消彼长,从整体上就会呈现出一种相对的确定性。这种趋势随着并行运行的处理的数量和不确定性增加而增强。
我们的游戏引擎设计为多线程多模块异步平行执行模式。每个模块的任务类型很不一样,执行频率也不同。比如,渲染模块需要逐帧运行,涉及到大量的大块内存使用,但是这些buffer往往生命周期很短;场景加载模块则相对来说以很长的周期运行,其数据结构可能会在内存当中保持数分钟甚至数十分钟;而AI等逻辑模块则是典型的计算模块,会涉及到大量小buffer的高频分配与释放。
于此同时,游戏场景是由场景物体组成的,我们的很多模块都需要以场景物体为单位进行处理。同一个模块对于不同场景物体的处理是类似的,也就是说对于内存的访问模式是类似的。我们可以很自然地把他们组织成为一个内存管理上的兄弟关系。
好,接下来就让我们把这些想法落实到代码当中。因为我们目前还没有其它模块,我们还不需要完成上面所设计的全部内容。我们先将我们上一篇所写的Allocator组织到我们的Memory Manager当中,提供一个最基本的,单层的但是支持不同分配尺寸的,线程不安全的内存管理模块。
代码主要参考了参考引用1,结合我们的架构与命名规则进行了封装,并且进行了跨平台方面的一些改造。
#pragma once #include "IRuntimeModule.hpp" #include "Allocator.hpp" #include <new> namespace My { class MemoryManager : implements IRuntimeModule { public: template<typename T, typename... Arguments> T* New(Arguments... parameters) { return new (Allocate(sizeof(T))) T(parameters...); } template<typename T> void Delete(T *p) { reinterpret_cast<T*>(p)->~T(); Free(p, sizeof(T)); } public: virtual ~MemoryManager() {} virtual int Initialize(); virtual void Finalize(); virtual void Tick(); void* Allocate(size_t size); void Free(void* p, size_t size); private: static size_t* m_pBlockSizeLookup; static Allocator* m_pAllocators; private: static Allocator* LookUpAllocator(size_t size); }; }
#include "MemoryManager.hpp" #include <malloc.h> using namespace My; namespace My { static const uint32_t kBlockSizes[] = { // 4-increments 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 64, 68, 72, 76, 80, 84, 88, 92, 96, // 32-increments 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 480, 512, 544, 576, 608, 640, // 64-increments 704, 768, 832, 896, 960, 1024 }; static const uint32_t kPageSize = 8192; static const uint32_t kAlignment = 4; // number of elements in the block size array static const uint32_t kNumBlockSizes = sizeof(kBlockSizes) / sizeof(kBlockSizes[0]); // largest valid block size static const uint32_t kMaxBlockSize = kBlockSizes[kNumBlockSizes - 1]; } int My::MemoryManager::Initialize() { // one-time initialization static bool s_bInitialized = false; if (!s_bInitialized) { // initialize block size lookup table m_pBlockSizeLookup = new size_t[kMaxBlockSize + 1]; size_t j = 0; for (size_t i = 0; i <= kMaxBlockSize; i++) { if (i > kBlockSizes[j]) ++j; m_pBlockSizeLookup[i] = j; } // initialize the allocators m_pAllocators = new Allocator[kNumBlockSizes]; for (size_t i = 0; i < kNumBlockSizes; i++) { m_pAllocators[i].Reset(kBlockSizes[i], kPageSize, kAlignment); } s_bInitialized = true; } return 0; } void My::MemoryManager::Finalize() { delete[] m_pAllocators; delete[] m_pBlockSizeLookup; } void My::MemoryManager::Tick() { } Allocator* My::MemoryManager::LookUpAllocator(size_t size) { // check eligibility for lookup if (size <= kMaxBlockSize) return m_pAllocators + m_pBlockSizeLookup[size]; else return nullptr; } void* My::MemoryManager::Allocate(size_t size) { Allocator* pAlloc = LookUpAllocator(size); if (pAlloc) return pAlloc->Allocate(); else return malloc(size); } void My::MemoryManager::Free(void* p, size_t size) { Allocator* pAlloc = LookUpAllocator(size); if (pAlloc) pAlloc->Free(p); else free(p); }
参考引用
- Memory Management part 2 of 3: C-Style Interface | Ming-Lun “Allen” Chou
- How tcmalloc Works
- Memory management
- operator new, operator new[]
本作品采用知识共享署名 4.0 国际许可协议进行许可。