说我有这样的玩具循环
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:
x[i-1], x[i], x[i+1]
y[i]
操作强度是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访问
x[i-1], x[i], x[i+1]
,存储x[i]
或仍然有2个RAM访问
x[i-1], x[i], x[i+1]
x[i]
因为在任何一种情况下,操作强度 OI 都会有所不同。谁能说出这个呢?或者澄清一些事情。感谢
答案 0 :(得分:2)
免责声明:直到今天我还没有听说过车顶线性能模型。据我所知,它试图计算算法“算术强度”的理论界限,该算法是每个访问数据字节的FLOPS数。这样的度量对于比较类似算法可能很有用,因为N
的大小变大,但对预测实际性能不是很有帮助。
作为一般经验法则,现代处理器可以比获取/存储数据更快地执行指令(随着数据开始变得大于缓存的大小,这变得非常明显)。因此,与人们的预期相反,具有较高算术强度的循环可能比具有较低算术强度的循环运行得快得多;最重要的是N
标度是触及的数据总量(只要内存保持明显慢于处理器,这将保持正确,就像今天常见的桌面和服务器系统一样)。
简而言之,遗憾的是,x86 CPU过于复杂,无法使用如此简单的模型进行准确描述。在访问RAM之前,对内存的访问会经过多层缓存(通常是L1,L2和L3)。也许所有数据都适合L1 - 第二次运行循环时,根本就没有RAM访问。
而且不只是数据缓存。不要忘记代码也在内存中,并且必须加载到指令缓存中。每个读/写也都是从一个虚拟地址完成的,这个地址由硬件TLB支持(在极端情况下可以触发页面错误,例如,导致操作系统在循环中间将一个页面写入磁盘) )。所有这一切都假设您的程序将硬件全部用于自身(在非实时操作系统中,情况并非如此,因为其他进程和线程正在竞争相同的有限资源)。
最后,执行本身不是(直接)完成内存读写,而是首先将数据加载到寄存器中(然后存储结果)。
编译器如何分配寄存器,如果它尝试循环展开,自动矢量化,指令调度模型(交错指令以避免指令之间的数据依赖性)等也将影响算法的实际吞吐量。
因此,最后,根据生成的代码,CPU模型,处理的数据量以及各种缓存的状态,算法的延迟将根据数量级而变化。因此,循环的操作强度不能通过单独检查代码(或甚至是生成的组件)来确定,因为还有许多其他(非线性)因素在起作用。
为了解决您的实际问题,尽管我可以在definition outlined here看到,第二个循环平均每次迭代计为一个额外的4字节访问,因此其OI将为θ( 3N FLOPS / 4N字节)。直观地说,这是有道理的,因为缓存已经加载了数据,并且写入可以直接更改缓存而不是返回主存储器(但最终必须写回数据,但是该要求与第一个要求相同)循环)。