Skip to content

计算受限与内存受限

说明:本文翻译自 CisMine Ng 在 Level Up Coding 发表的博客 Compute Bound & Memory Bound,版权归原作者所有。

在任何一个程序中,我们都需要做两件事:

  • 从内存中取出数据。
  • 对数据执行计算。

或者说:当讨论一段代码的性能时,我们主要考虑两个概念:内存计算

什么是内存和计算?为什么它们如此重要?

内存 — 计算

  • 计算(Computation):指计算能力,通常用一个流行的指标来衡量,即 FLOPS 率(floating point operations per second,每秒浮点运算次数)。它量化了计算机在一秒内执行浮点运算的性能:
Flop Rate=总浮点运算次数 (Total Floating-Point Operations)执行时间 (Execution Time)

图:FLOPS(计算能力)示意图

  • 内存(Memory):这里指的不是总内存容量,而是 内存带宽(GB/s),即数据在内存与处理单元之间加载或存储的速率:
Memory Bandwidth=总线宽度 (Bus Width)×时钟频率 (Clock Speed)×效率因子 (Efficiency Factor)

图:内存带宽示意图

如何确定一个良好的 FLOPS 率或内存带宽?

期望的计算与内存比率(OP/B)

OP/B=Operations (OP)Bytes Transferred (B)

这是一个关键指标,用于平衡计算机的处理能力与其内存的加载/存储能力。

图:OP/B 比率示意图

为什么要平衡它们?——平衡能确保硬件资源得到高效利用,避免性能瓶颈。

  • 如果 OP/B 偏低:系统在处理繁重的计算任务,但计算能力有限,这会导致计算受限(compute-bound)的情形。
  • 如果 OP/B 偏高:系统无法提供足够的数据供处理,导致数据饥饿(data starvation),即内存受限(memory-bound)的情形。

什么是计算/内存受限?如何识别并解决它们?

  • 计算受限(Compute-bound):当计算机的性能受限于其计算能力时发生,在执行复杂计算时很常见。
  • 内存受限(Memory-bound):当性能受限于从内存访问数据的能力时发生,通常在处理大量数据的加载/存储操作时出现。

在代码中识别计算/内存受限

光速吞吐量(Speed Of Light Throughput, SoL)

图:Nsight Compute 中的 SoL 示例

Nsight Compute 可以利用 SoL 帮助我们识别代码是计算受限还是内存受限。

SoL:实际利用率相对于理论上限的百分比,它反映的是计算机硬件的活跃程度,而不是代码本身的性能。

我们的目标是确保计算和内存资源得到均衡利用,避免显著失衡。

  • 均衡利用可以防止瓶颈,即避免某一资源(计算或内存)成为整体性能的限制因素。
  • 均衡利用还能最大限度地提高硬件效率,使实际性能更接近理论峰值性能。

图:SoL 的四种典型情况

  • 延迟(Latency)(M 与 SM 均 < 60%): 如上所述,SoL 反映的是计算机的活跃程度。但在这种情况下,我们可以看到 M(内存)和 SM(计算)都没有得到充分利用。这表明系统没有充分利用可用资源,可能意味着工作负载不够重,不足以压满硬件。

  • 计算受限(Compute Bound)(SM > 60% 或 M < 60%): 这种情况表明,系统虽然拥有足够的计算能力来处理数据,但计算单元被复杂的运算过度占用,导致计算受限。

  • 内存受限(Memory Bound)(SM < 60% 或 M > 60%): 这会导致数据饥饿,即系统无法及时为计算资源提供足够的数据,尽管计算本身可能很简单。

  • 计算/内存同时受限(Compute/Memory Bound)(SM 与 M 均 > 60%): 在这种情况下,系统的计算和内存都处于高负载运行,需要仔细监控性能,以避免潜在的瓶颈。

图:Nsight Compute 中的资源利用率汇总界面

总之,上述每种情况都反映了不同类型的资源失衡:

  • 延迟(Latency):两种资源利用率都很低。
  • 计算受限(Compute Bound):计算过度使用,而内存利用不足。
  • 内存受限(Memory Bound):内存利用率高,而计算利用不足。
  • 均衡利用(Balanced Usage):计算和内存都得到优化利用,或两者同时达到性能上限。

在 Nsight Compute 中,可以通过以下两个指标来判断瓶颈类型:

  • SM: Inst Executed Pipe Lsu(%)(SM 执行的 LSU 指令流水线利用率):如果这个百分比很高(出现在 SM 侧而非计算侧),说明加载/存储单元耗时较大。
  • SM: Pipe Fma/Alu Cycles Active (%)(SM 的 FMA/ALU 流水线活跃周期):SM 计算活跃度百分比。

Roofline 模型(Roofline chart)

图:Nsight Compute 中的 Roofline 图

Nsight Compute 可以利用 Roofline 图帮助我们识别代码是计算受限还是内存受限。

在深入讲解 Roofline 图之前,先介绍你需要了解的定义。

  • 算术强度(Arithmetic Intensity):一个衡量计算资源(FLOP,浮点运算次数)与内存带宽(传输的数据字节数)利用效率的指标:
AI=总 FLOP总 DRAM 字节数=ComputationBytes fetched from memory

图:算术强度定义示意图

我们知道,数学运算比内存访问快得多,因此需要合理平衡这一比率,以避免内存/计算受限的情况。每台计算机的这一比率都不同,要确定它,可以点击图中所示的方块。

图:如何查看本机的计算与内存比率

我们详细分析一下这张图:

图:Roofline 图(来源:NERSC Roofline 文档

  • 峰值 FLOP/s(Peak FLOP/s):计算机所能达到的最大计算速度。
  • 带宽 GB/s(Bandwidth GB/s):计算机加载/存储内存的速率,在红线与蓝线相交处达到峰值。该点称为关键点(key point)拐点(knee point)

关键点(拐点):两个阶段之间发生转变的点:

  • 内存受限阶段。
  • 计算受限阶段。

理论上:如果我们达到关键点比率(如图所示,AI = 0.55),意味着我们的代码近乎完美(在计算与内存之间取得了平衡)。

实践中:达到图中对角线上的某一点就已经是非常好的结果。

瓶颈情形:

P=min(Ppeak, Ib)
  • P(FLOP/s):表示执行一个任务的速度。
  • Ppeak(峰值):计算机理论上能达到的最大计算速度。
  • Ib(FLOP/byte × byte/s):运行特定代码所需的实际速度。

图:求取 P 的公式

当我们用 min 函数判断系统是计算受限还是内存受限时:

  • 如果 Ppeakmin 中的较小项 → 计算受限:这意味着运行代码所需的实际速度更高,但计算机受限于峰值计算速度,无法超越它。解决方案:使用更大的单元尺寸(unit size)。
  • 如果 Ibmin 中的较小项 → 内存受限:这表明我们尚未充分利用计算机的能力。解决方案:使用粗化(coarsening)策略。

因此,通过这两个方面,我们可以判断代码是存在计算问题,还是加载/存储数据的问题。在接下来的文章中,我将指导你如何针对每种情况具体解决。

参考

Maintained by Robin