将存储指针放在数组中受益于CPU缓存加速?

时间:2014-09-30 11:20:33

标签: c performance caching c++11

据我所知,理论上如果我将数据存储在连续内存中,例如数组,当数组的第一个元素被cpu读取时,由于跨步,cpu可以将整个数组加载到其L1缓存中(如果数组可以适合L1缓存),因此在读取其他数组元素时减少了缓存未命中。

但是,如果数组存储指针,并且cpu必须取消引用指针并对其执行某些操作,这将导致内存的其他部分被加载到L1 cahce。 例如,如果我的L1数据缓存是32KB,并且我有一个8K元素的数组:

MyObject* myarray[8192];
//...
for (int i=0; i < 8192; ++i) {
    MyObject& obj = *myarray[i];
    obj.doSomethingComplicated();
}

在这种情况下,myarray的总大小将是4x8 = 32KB,我是否正确执行doSomethingComplicated()时,至少部分L1缓存可能会被删除?因此,试图限制myarray大小以使其适合L1缓存是没有意义的吗?

实际上,在为myarray选择相对较小的尺寸时,我仍然可以看到一个小但可重复测量的性能提升,但我无法解释原因。

我只关心x86_64,因为我对其他平台完全不熟悉。

2 个答案:

答案 0 :(得分:3)

我认为没有一个简单的答案。

  1. 缓存适用于名为缓存行的内存区域。对于现代Intel CPU,高速缓存行有64个字节,这就是高速缓存将在其上运行的内容(加载,存储)。因此,它可能不会将所有数组加载到缓存中,即使它适合。

  2. 数据和指令有一个单独的L1缓存。在您的情况下,myarray将被加载到数据缓存,但该函数的代码将被加载到指令缓存中。函数的代码在类的所有对象之间共享,因此除非使用多态,否则代码将被加载一次到缓存。

  3. 当然有班级成员。 doSomethingComplicated方法可能对它们进行操作,因此必须再次以64B块的形式将数据加载到(数据)缓存中。因此,该方法操作的数据布局很重要。

  4. 总而言之,我的建议是:根据上述信息,做一些实验并衡量你的案例的表现。

答案 1 :(得分:1)

如果&#39; doSomethingComplicated&#39;你几乎可以确定指针数组的某些部分被推出L1。触及任何内存。被驱逐使用伪LRU算法,伪部分是它在一组基础上发生,即。共享地址最低部分的缓存行。

您的指针数组很好(如果您喜欢指针)并且在这种情况下它会以接近最佳的方式运行,因为它将按顺序使用,您的问题也将是它所指向的也应按顺序访问以获得最佳性能。

这意味着&#39; MyObject&#39;最好是在一个数组本身,在这种情况下,原始指针变得毫无意义!因为指数会好或者更好。

MyObject MyObjectArray[8192];

如果没有顺序访问它们,你可能会在指针追逐领域,这是坏的。但至少他们有一些空间位置,因为它们占据了连续的记忆区域。

1级缓存使用2个预取程序,这使得连续内存访问的行为非常好,有效地使所有内存在L1延迟时通过它们进行访问。在最新的Intel L2缓存中,可以为L1提供2个内存请求,但每个周期只从L3获得一个,L3为所有L2缓存提供服务,因此如果每个周期发出多个请求,它们必须排队。 L3每隔X个周期只能由一个请求由其他L3(或L4)高速缓存或内存提供服务。

所以只要myarray和MyObjectArray 所有其他内存都可以复制&#39;或称为触摸的函数可以适合L2,你的程序运行速度非常快。一旦你需要3个预取器或转到L3 /内存,所有都会慢下来。

因此,一切都不出所料,取决于“做某事”的行为。