3、显存池性能指标:分配延迟、碎片率、吞吐量、内存利用率

聊显存池,绕不开四个核心指标。

分配延迟、碎片率、吞吐量、内存利用率。

这四个指标,说白了就是衡量一个显存池「好不好用」的尺子。我这些年调优踩过的坑,十有八九都跟它们有关。今天咱们一个一个掰开揉碎了讲。

3.1 分配延迟:你的模型在等内存吗?

分配延迟,就是申请一块显存需要花多长时间。

你想想看,如果每次 new 一个 Tensor 都要等上几毫秒,那训练速度肯定被拖死。我见过一个项目,模型结构特别复杂,每步都要动态创建几十个临时 buffer。结果呢?GPU 算力利用率不到 30%,全耗在 cudaMalloc 上了。

核心观点: 分配延迟决定了显存池的「响应速度」。对于高频小内存分配场景,延迟必须控制在微秒级。

怎么衡量?我一般用这个公式:

分配延迟 = 单次分配耗时 / 分配次数

实际测试时,我会写一个简单的 benchmark:

// 伪代码示例
cudaEvent_t start, stop;
cudaEventCreate(&start);
cudaEventCreate(&stop);

cudaEventRecord(start);
for (int i = 0; i < 10000; i++) {
    void* ptr = pool->allocate(1024); // 从池中分配 1KB
    pool->deallocate(ptr);
}
cudaEventRecord(stop);
cudaEventSynchronize(stop);

float ms;
cudaEventElapsedTime(&ms, start, stop);
printf("平均分配延迟: %.3f us\n", ms * 1000 / 10000);
我的经验: 分配延迟低于 1 微秒算优秀,1-5 微秒可接受,超过 10 微秒就要警惕了。我曾经把一个 8 微秒的池子优化到 0.5 微秒,整网吞吐量直接提升了 15%。

3.2 碎片率:显存里的「黑洞」

碎片率,是显存池最头疼的问题。

什么叫碎片?就是你明明还有 8GB 空闲显存,但申请一个 2GB 的连续块却失败了。这些空闲空间被分割成无数小块,像棋盘一样散落在显存里。

我习惯用这个公式计算碎片率:

碎片率 = 1 - (最大连续空闲块大小 / 总空闲大小)

举个例子:

场景 总空闲 最大连续块 碎片率
理想状态 8 GB 8 GB 0%
轻度碎片 8 GB 6 GB 25%
重度碎片 8 GB 1 GB 87.5%
避坑指南: 我曾经在一个推理服务里遇到过碎片率飙到 90% 的情况。原因是模型每次推理都分配不同大小的中间结果,池子里的块被切得七零八落。最后我改用 buddy 分配器 + 定期整理,才把碎片率压到 5% 以下。

碎片率高了会怎样?

  • 大块分配失败,触发 fallback 到 cudaMalloc,延迟飙升
  • 显存利用率虚高,实际可用空间缩水
  • 严重时直接 OOM,进程崩溃

3.3 吞吐量:池子能扛住多大压力?

吞吐量,衡量的是显存池在单位时间内能处理多少分配/释放请求。

这个指标跟分配延迟有点像,但侧重点不同。延迟看的是单次,吞吐量看的是并发压力下的整体表现。

我一般用这个公式:

吞吐量 = 总分配次数 / 总耗时

单位通常是「百万次/秒」或者 MOPS(Million Operations Per Second)。

关键点: 吞吐量受锁竞争影响极大。多线程场景下,如果显存池内部用了一把大锁,那吞吐量会直线下降。

我遇到过最夸张的一次:一个显存池单线程吞吐量 50 MOPS,换成 8 线程后掉到了 8 MOPS。原因就是内部用了全局锁,所有线程都在等锁释放。

怎么优化?

  • 无锁设计: 用原子操作替代锁,或者用 per-thread cache
  • 分桶策略: 不同大小的分配走不同的 freelist,减少竞争
  • 批量处理: 把多次小分配合并成一次大分配

3.4 内存利用率:别让显存「空转」

内存利用率,就是池子里有多少显存被真正用上了。

公式很简单:

内存利用率 = 已分配大小 / 池子总大小

但这里有个坑——利用率不是越高越好。

你想想看,如果把利用率压到 99%,那意味着池子里几乎没有空闲块。一旦有新的分配请求进来,要么等别人释放,要么触发昂贵的整理操作。结果就是延迟飙升。

我的建议: 内存利用率控制在 80%-90% 是比较健康的区间。低于 80% 说明池子太大,浪费显存;高于 95% 说明池子太满,容易出问题。

我见过一个团队把利用率推到 98%,结果线上频繁出现分配超时。后来我帮他们加了 10% 的预留空间,问题就解决了。

3.5 四个指标的权衡关系

这四个指标不是孤立的,它们互相牵制。

我画了一张图,帮你理清它们的关系:

显存池性能指标 分配延迟 碎片率 吞吐量 内存利用率 影响 影响 影响 影响 负相关 正相关 碎片越多,延迟越高 利用率高,吞吐下降 虚线表示影响关系,实线表示指标间的权衡

从图上你能看到:

  • 分配延迟 vs 吞吐量: 延迟越低,吞吐量越高。但优化延迟可能会增加锁竞争,反而降低吞吐量
  • 碎片率 vs 内存利用率: 利用率越高,碎片越容易产生。这是个天然的矛盾
  • 碎片率 vs 分配延迟: 碎片多了,找合适块的时间变长,延迟自然就上去了
  • 吞吐量 vs 内存利用率: 池子太满,分配时容易触发整理或等待,吞吐量会掉
总结一下: 没有完美的显存池,只有适合场景的权衡。我的做法是——先确定业务对延迟和吞吐量的要求,再在碎片率和利用率之间找平衡点。

嗯,这四个指标讲完了。下次你评估一个显存池好不好,就从这四个维度去测。保证能看出门道来。


想要更多资料,联系可可