昇腾社区首页
中文
注册

内存池部分介绍

在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

    分配内存块方法,主要逻辑分为两个步骤:

    1. 在空闲块中查找是否有符合要求大小的内存块,找到了则将内存块索引返回,并将此内存块标记为已使用。
    2. 如果没有符合要求的内存块,但预申请内存还有剩余空间。先将剩余内存空间的首地址进行64字节对齐,在此基础上将申请分配大小向上对齐成32整数倍+32字节,构建内存块标记为已使用并返回索引。
  • MemoryPool::FreeBlock

    释放内存块时,不直接释放内存,只是将其标记为未使用。

使用内存池管理workspace内存

实现内存池的首要目的是减少aclrtMalloc和acletFree的调用次数,在本例中主要针对workspace的内存进行管理。

在Node类中会保存有当前已分配的workspace大小和对应的blockId。当需求的workspace大小发生变化时,如果需求大小小于当前已有空间则不进行操作,大于则释放当前内存块并重新申请更大的内存块。