有多少对象(包含 std::vectors)加载到 L1/L2/L3 缓存中

How much of an object (containing std::vectors) is loaded in to the L1/L2/L3 cache?

本文关键字:L1 L2 缓存 L3 加载 对象 多少 包含 vectors std      更新时间:2023-10-16

请参阅以下链接,第 22 页起:

http://research.scee.net/files/presentations/gcapaustralia09/Pitfalls_of_Object_Oriented_Programming_GCAP_09.pdf

上面的链接建议我是否有一个包含向量/数组的对象,如下所示:

class MyClass{
    public:
    double a[1000];
    double b[1000];
};

下面的代码遍历 MyClass 的一个向量,并在 std::vector b 上执行一些数学运算:

std::vector<MyClass> y;
y.populateVector();
for(auto x : y){
    //Iterate though x.b and do some math;
    for(int i=0; i<1000; i++){
        std::cout << x.b[i] << std::endl;
    }
}

当我们检索每个 MyClass 对象时,来自两个数组的所有数据都将加载到缓存行中。这是真的吗?我不认为a的数据会被加载到缓存行中,因为访问b的地址将被计算和加载。

我试图了解与处理所需的有用数据相比,缓存中加载了多少 MyClass 对象?

我可以理解第一个b元素是否与最后一个元素共享相同的缓存行a但我不认为整个对象会加载到 L2/L3 缓存中只是为了处理对象的一部分?

您的声明:

for(auto x : y) ...

x声明为值而不是引用。 编译器可能会优化将y的每个元素复制到局部变量x,但我不会指望它。

如果你写:

for(auto &x : y) ...

然后循环将处理对 y 中对象的引用。 我假设这就是你的意思。

具体来说,忽略结构填充:编译器将转换

double temp = y[i].b[j];

变成等效的东西

double temp = *(
    y.data() + i * sizeof(MyClass) // start of y[i]
    + 1000 * sizeof(double)        // skip over y[i].a
    + j * sizeof(double));         // get to the right place in y[i].b

它会将包含该地址的缓存行大小的块加载到缓存行中。

然后,当您迭代y[i].b 的更多元素时,其中许多元素已经存在于缓存中。

由于每个数组包含 1000 个元素,因此它们比典型 CPU 上的缓存行大得多。 1000 个双精度占用 8000 字节,而 Sandy Bridge 体系结构上的缓存行(例如)为 64 字节。 遍历数组将有效地使缓存饱和。 您可能会在 x.a 的第一个和最后一个元素上浪费部分缓存行,但影响应该很小。 随着数组大小的增加,这些浪费负载的重要性接近 0。

Playstation文章讨论了大小与缓存行相当的对象。 对于像您这样的大型对象,这些优化并不重要。

取决于系统上内存的组织方式。如果碰巧ab的后备阵列位于内存中非常近的位置(因为 CPU 通常会发出更大的读取来填充缓存,希望您使用它),则它们可能会被加载。如果没有,我认为没有理由阅读b除了尝试从类实际驻留在内存中的位置读取一些指针之外,a没有任何意义。

它确实表明,以随意的方式使用类可以并且将导致缓存未命中,因为它们驻留在内存中的方式。

加载到缓存中的一般规则是,如果 CPU 发出读取并错过缓存,它将从主内存加载缓存对齐的块(在示例中为 128 字节)。

对于您编辑的示例,是的,这些是共置的内存片段,如果仅仅因为它们在内存中的位置而发出对b的读取,则可能会加载a部分。

对于您的示例,每个 MyClass 对象都包含一个 2000 * sizeof(double) 字节的连续区域(很可能对齐)。这些对象被打包到由向量指向的连续内存区域中。访问每个对象的b成员将导致缓存未命中(如果未缓存)。缓存对齐的内存片段的内容将从错过缓存的每个读取加载。根据内存对齐约束和缓存大小,a成员中的某些条目可能会加载到内存中。甚至可以假设,由于填充和对齐,您的任何MyClass a成员都不会被加载到缓存中(并且没有理由将它们加载到缓存中,因为它们没有被访问)。

在您引用的链接中,两个数组 ab 是 4x4 矩阵,这意味着每个数组有 16 个元素。由于这是关于视频游戏的,因此它们可能是浮点数。16 个浮点数占用 64 个字节。CPU 高速缓存行为 128 字节。因此,a的很大一部分很有可能与b[0]在同一缓存行中。据统计,50% 的a将位于与b[0]相同的缓存行中。然后,读取b[0]会将该部分a加载到缓存中。如果您设法在 128 字节上对齐类/结构,您甚至可以保证 ab 完全适合同一缓存行。

现在在您的示例中,您不使用 16 个浮点数,而是使用 1000 个双精度。这是 8000 字节,比典型的缓存行大得多。a的最后几个元素可能与b[0]位于同一缓存行中,但影响很小。