【问题标题】:Roofline model: calculating operational intensityRoofline 模型:计算运营强度
【发布时间】:2017-04-06 19:04:18
【问题描述】:

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

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 字节(即足够大)。然后我将(每帧)基本上 2 次访问 RAM 和 3 次 FLOP:

  • 1(缓存)读取访问:加载所有 3 x[i-1], x[i], x[i+1]
  • 1个写访问:存储y[i]
  • 3 FLOP (1 mul, 1 add, 1 sub)

操作强度是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 访问权限

  • 1(缓存)读/写:加载x[i-1], x[i], x[i+1],存储x[i]

或仍然有 2 个 RAM 访问

  • 1(缓存)读取:加载x[i-1], x[i], x[i+1]
  • 1(缓存)写入:存储x[i]

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

【问题讨论】:

  • 这些循环是不等价的——当你写信给x[k]时,你会影响x[k+1]的计算值。这可以接受吗?
  • 对不起,这个。逻辑应该是无关紧要的。我只是重复使用相同的循环来突出我的问题。

标签: c++ performance memory-bandwidth roofline


【解决方案1】:

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

作为一般经验法则,现代处理器执行指令的速度比它们获取/存储数据的速度要快得多(随着数据开始增长到超过缓存大小,这种情况变得更加明显)。因此,与预期相反,具有较高算术强度的循环可能比具有较低算术强度的循环运行得快得多。对于N 而言,最重要的是所触及的数据总量(只要内存仍然明显慢于处理器,就像当今常见的桌面和服务器系统一样,这将保持不变)。

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

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

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

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

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


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

【讨论】:

    猜你喜欢
    • 2015-11-14
    • 2011-03-17
    • 2011-03-10
    • 2011-09-22
    • 2020-06-21
    • 2011-01-03
    • 1970-01-01
    • 2011-12-02
    • 2010-11-19
    相关资源
    最近更新 更多