Roofline 模型:计算运营强度

Roofline model: calculating operational intensity

假设我有一个这样的玩具循环

float x[N];
float y[N];
for (int i = 1; i < N-1; i++)
    y[i] = a*(x[i-1] - x[i] + x[i+1])

我假设我的缓存行是 64 Byte(即足够大)。然后我将(每帧)基本上有 2 次访问 RAM 和 3 次 FLOP:

运算强度为ergo

OI = 3 FLOP/(2 * 4 BYTE)

现在如果我这样做会发生什么

float x[N];
for (int i = 1; i < N-1; i++)
    x[i] = a*(x[i-1] - x[i] + x[i+1])

请注意,不再有 y。这是否意味着现在我有一个单一的 RAM 访问

或者还有 2 次 RAM 访问

因为操作强度OI在任何一种情况下都会不同。任何人都可以告诉这件事吗?或者澄清一些事情。谢谢

免责声明:直到今天我才听说过 roofline 性能模型。据我所知,它试图计算算法 "arithmetic intensity" 的理论界限,即访问的每字节数据的 FLOPS 数。随着 N 的大小变大,这种度量可能有助于比较类似算法,但对于预测真实世界的性能不是很有帮助。

作为一般经验法则,现代处理器执行指令的速度比 fetch/store 数据快得多(随着数据开始增长到大于缓存的大小,这一点变得更加明显)。因此,与人们可能期望的相反,具有较高算术强度的循环可能 运行 比具有较低算术强度的循环快得多;随着 N 规模的扩大,最重要的是接触的数据总量(只要内存仍然比处理器慢得多,这一点就会成立,就像当今常见的台式机和服务器系统一样)。

简而言之,不幸的是,x86 CPUs 太复杂了,无法用如此简单的模型准确描述。在到达 RAM 之前,对内存的访问会经过几层缓存(通常是 L1、L2 和 L3)。也许你所有的数据都适合 L1——你第二次 运行 你的循环可能根本没有 RAM 访问。

而且不仅仅是数据缓存。不要忘记代码也在内存中并且必须加载到指令缓存中。每个 read/write 也完成 from/to 一个虚拟地址,该地址由硬件 TLB 支持(在极端情况下会触发页面错误,例如导致 OS 写入页面到循环中间的磁盘)。所有这一切都假设您的程序正在占用所有硬件(在非实时 OSes 中根本不是这种情况,因为其他进程和线程正在竞争相同的有限资源)。

最后,执行本身并不是(直接)完成内存读写,而是先将数据加载到寄存器中(然后存储结果)。

编译器如何分配寄存器、是否尝试循环展开、自动矢量化、指令调度模型(交错指令以避免指令之间的数据依赖)等也会影响算法的实际吞吐量。

因此,最后,根据生成的代码、CPU 模型、处理的数据量以及各种缓存的状态,算法的延迟将按数量级变化。因此,不能仅通过检查代码(甚至生成的程序集)来确定循环的操作强度,因为还有许多其他(非线性)因素在起作用。


不过,为了解决您的实际问题,据我在 definition outlined here 中所见,第二个循环平均算作每次迭代的一个额外的 4 字节访问,因此它的 OI 将是θ(3N FLOPS / 4N 字节)。直观上,这是有道理的,因为缓存已经加载了数据,写入可以直接更改缓存而不是返回主内存(数据最终确实必须写回,但是,这个要求与第一次没有变化循环)。