Intel Discrete GPU - Memory - Evict / Swap
Linux 内核内存管理子系统中为系统内存提供了 Kswapd 和 Direct Reclaim 两种内存回收方式,以缓解系统内存 Free 余量不足所带来的内存分配失败问题,然而这种内存回收仅仅面向系统内存范围。如果独立显存遇到类似的空闲显存余量不足导致 APP 分配显存失败的场景怎么办?
TTM 早就想到了这一点,它为 ttm device 提供了 Evict 能力及 Swap 能力。所谓 Evict 代表从独立显存驱逐一部分可能暂时使用不到(经由 LRU 筛选)的显存至系统内存,Swap 代表系统内存如果存在内存压力的情况下可以将系统内存中显存所占用的部分通过内存子系统的回收途径将其 Swapout 至后备存储。
现在我们能够得知的是,TTM 为独立显存所提供的能力亦可以将显存从独立显存中回收至内核的内存后备存储中,这点与普通内存的流程相同。但回收的触发时机与普通内存稍有不同,当前 TTM 软件架构中未实现 Kswape 这种提供异步回收显存的方式,Evict 或 Swap 仅在显存分配可能失败时被触发,这点类似于普通内存的 Direct Reclaim;
先来看一下关于显存回收的全局框图,

下面我们稍详细的解读下两步显存回收流程,值得关注的点是显存回收目标的选择方式与显存的流转过程,
LRU
TTM 架构中显存回收的目标选择也是使用比较常见的 LRU 算法,算法中比较关键的流程在于 LRU 的更新,或者可以称之为老化操作,时间长未使用到的显存越容易被回收。先看来看一下 TTM 架构中操作到 LRU 的几个流程点,
-
加入 - LRU 的加入在显存分配的过程中,以独立显存为例,它在
ttm_resource_init中加入,核心实现如下,man = ttm_manager_type(bo->bdev, place->mem_type); spin_lock(&bo->bdev->lru_lock); if (bo->pin_count || ttm_resource_is_swapped(res, bo)) list_add_tail(&res->lru.link, &bo->bdev->unevictable); else list_add_tail(&res->lru.link, &man->lru[bo->priority]); man->usage += res->size; spin_unlock(&bo->bdev->lru_lock); -
老化 - 这一步作为整个算法的重中之重,我们需要识别出哪些显存比较活跃,将这些显存向 LRU 链表中不容易回收的方向移动。在 Xe 驱动中为 GPU 提供了两种显存模式,分别是缺页模式和非缺页模式,
- 在非缺页模式下,在用户态驱动没有明确告知每包指令所涉及的显存时,GPU 并不能哪部分显存设置为活跃较为合适,所以当前的做法是在应用下属指令时,将应用对应的所有显存都推进至活跃状态,也就是将显存在 LRU 链表中移动到尾部位置,TTM 所提供的接口为
ttm_lru_bulk_move_tail,核心实现为
for (i = 0; i < TTM_NUM_MEM_TYPES; ++i) { for (j = 0; j < TTM_MAX_BO_PRIORITY; ++j) { struct ttm_lru_bulk_move_pos *pos = &bulk->pos[i][j]; ... man = ttm_manager_type(pos->first->bo->bdev, i); list_bulk_move_tail(&man->lru[j], &pos->first->lru.link, &pos->last->lru.link); } }在显存 move 至 LRU 尾部的过程中,我们能看到流程中并不是显存依次单个 move,而是采用 bulk 的方式,这里其实是 AMD 在 2018 年引入的一个 LRU 更新优化,具体介绍可见Youtube链接;
- 在缺页模式下,
- 在非缺页模式下,在用户态驱动没有明确告知每包指令所涉及的显存时,GPU 并不能哪部分显存设置为活跃较为合适,所以当前的做法是在应用下属指令时,将应用对应的所有显存都推进至活跃状态,也就是将显存在 LRU 链表中移动到尾部位置,TTM 所提供的接口为
-
移除 - 当显存释放时,将会从 LRU 中移除,以独立显存为例的话,其释放接口为
xe_ttm_vram_mgr_del,涉及到的 LRU 移除操作如下,spin_lock(&bdev->lru_lock); list_del_init(&res->lru.link); man->usage -= res->size; spin_unlock(&bdev->lru_lock);