# 5.11.1 寄存器溢出

循环并行性的好处受汇编代码描述计算的能力限制。如果我们的并行度 p 超过了可用的寄存器数量,那么编译器会诉诸溢出(spilling),将某些临时值存放到内存中,通常是在运行时堆栈上分配空间。举个例子,将 combine6 的多累积变量模式扩展到 k=10 和 k=20,其结果的比较如下表所示:

函数/界限 方法 整数 + 整数 * 浮点数 + 浮点数 *
combine6 10×10 循环展开 0.55 1.00 1.01 0.52
20×20 循环展开 0.83 1.03 1.02 0.68
吞吐量界限 0.50 1.00 1.00 0.50

我们可以看到对这种循环展开程度的增加没有改善 CPE,有些甚至还变差了。现代 x86-64 处理器有 16 个寄存器,并可以使用 16 个 YMM 寄存器来保存浮点数。一旦循环变量的数量超过了可用寄存器的数量,程序就必须在栈上分配一些变量。

例如,下面的代码片段展示了在 10×10 循环展开的内循环中,累积变量 acc0 是如何更新的:

# Updating of accumulator acc0 in 10 x 10 unrolling
vmulsd (%rdx), %xmm0, %xmm0  # acc0 *= data[i]

我们看到该累积变量被保存在寄存器 %xmm0 中,因此程序可以简单地从内存中读取 data[i],并与这个寄存器相乘。

与之相比,20×20 循环展开的相应部分非常不同:

# Updating of accumulator acc0 in 20 x 20 unrolling
vmovsd 40(%rsp), %xmm0
vmulsd (%rdx), %xmm0, %xmm0
vmovsd %xmm0, 40(%rsp)

累积变量保存为栈上的一个局部变量,其位置距离栈指针偏移量为 40。程序必须从内存中读取两个数值:累积变量的值和 data[i] 的值,将两者相乘后,将结果保存回内存。

一旦编译器必须要诉诸寄存器溢出,那么维护多个累积变量的优势就很可能消失。幸运的是,x86-64 有足够多的寄存器,大多数循环在出现寄存器溢出之前就将达到吞吐量限制。