内存池部分介绍
在Ascend Transformer Boost加速库(以下简称ATB)的调用流程中,往往需要频繁调用Malloc和Free向设备申请和释放内存空间,这样的操作会打断设备的流水,造成额外的性能开销。
内存池
内存池是一种常见的技术手段用于管理和分配设备的内存。其基本功能原理如下:
- 内存池预先申请一大块连续的内存的空间,并将其分割为多种粒度的小块。
- 需要申请内存时,从空闲块中找到合适的块直接分配。
- 如果找不到合适的块,进行内存回收、申请扩展空间等操作获得合适的块。
- 内存释放时,将使用块标记为空闲,回收至内存池。
主要结构
- MemoryBlock:
一个Block至少需要三个元素,用于定位的索引、块大小描述和对应的实际物理地址。
1 2 3 4 5 6 7 8 9 10
struct MemoryBlock { // blockId,内存块索引,全局唯一 int64_t blockId; // blockSize,单位为字节 size_t blockSize; // 内存块的物理地址 void *address = nullptr; };
- MemoryPool:
最基本的内存池,分别管理每个设备上的内存,一般其创建个数与设备数量对应。通常使用unordered_map的数据结构管理所有的block。它包含三个类方法,AllocateBlock、FreeBlock和GetBlockPtr,用于分配block、释放block和获取block中的内存地址。
- MemoryManager:
内存管理类,负责初始化所有设备的MemoryPool,并将其放入一个数组中统一维护。当不同线程或进程访问该类并调用方法时,通过GetDeviceId方法获得线程对应的设备ID,并返回对应设备内存池的相应方法。
关键函数
- MemoryPool::AllocateBlock
分配内存块方法,主要逻辑分为两个步骤:
- 在空闲块中查找是否有符合要求大小的内存块,找到了则将内存块索引返回,并将此内存块标记为已使用。
- 如果没有符合要求的内存块,但预申请内存还有剩余空间。先将剩余内存空间的首地址进行64字节对齐,在此基础上将申请分配大小向上对齐成32整数倍+32字节,构建内存块标记为已使用并返回索引。
- MemoryPool::FreeBlock
释放内存块时,不直接释放内存,只是将其标记为未使用。
使用内存池管理workspace内存
实现内存池的首要目的是减少aclrtMalloc和acletFree的调用次数,在本例中主要针对workspace的内存进行管理。
在Node类中会保存有当前已分配的workspace大小和对应的blockId。当需求的workspace大小发生变化时,如果需求大小小于当前已有空间则不进行操作,大于则释放当前内存块并重新申请更大的内存块。
父主题: ATB使用示例