请参阅以下链接,第22页:
上面的链接建议我是否有一个包含这样的矢量/数组的对象:
class MyClass{
public:
double a[1000];
double b[1000];
};
并且下面的代码遍历MyClass的向量并在std :: vector b上执行一些Math:
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缓存中以便处理对象的一部分?
答案 0 :(得分:3)
您的声明:
for(auto x : y) ...
将x
声明为值而不是引用。编译器可以优化将y
的每个元素复制到局部变量x
中,但我不会指望它。
如果你写:
for(auto &x : y) ...
然后循环将对y
中对象的引用起作用。我假设你的意图是什么。
具体而言,忽略struct padding:编译器将转换
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文章讨论了与缓存行大小相当的对象。对于像你这样的大型对象,这些优化不会起到同样的作用。
答案 1 :(得分:2)
取决于系统上内存的组织方式。如果碰巧a
和b
的支持数组非常靠近内存(因为CPU通常会发出更大的读取来填充缓存,希望你使用它)它是&#39;它们可能会被装载。如果不是,我认为没有理由阅读b
除了试图读取类实际驻留在内存中的某些指针之外,与a
有任何关系。
它显示的是,以随意方式使用类可能并且将导致缓存未命中仅仅因为它们驻留在内存中的方式。
加载到缓存中的内容的一般规则是,如果CPU发出读取并错过缓存,它将从主内存加载缓存对齐的块(在示例中有128个字节)。
对于您编辑的示例,是这些是共处的内存片段,如果仅因为它们在内存中的位置而发出对a
的读取,则可以加载部分b
。
对于您的示例,每个MyClass
对象由2000 * sizeof(double)
字节的连续区域组成(很可能是对齐的)。这些对象被打包到向量指向的连续内存区域中。访问每个对象的b
成员将导致缓存未命中(如果未缓存)。高速缓存对齐的内存块的内容将从每次错过高速缓存的读取中加载。根据内存对齐约束和高速缓存大小,a
成员中的某些条目可能会被加载到内存中。甚至可以假设由于填充和对齐,您的MyClass
a
成员中的任何一个都不会被加载到缓存中(并且会出现这种情况)他们没有理由因为他们没有被访问过。)
答案 2 :(得分:1)
在您所指的链接中,两个数组a
和b
是4x4矩阵,这意味着每个16个元素。由于这是关于视频游戏,它们可能是浮点数。 16个浮点数占用64个字节。
CPU缓存行为128个字节。因此,a
的很大一部分很可能与b[0]
位于同一缓存行中。从统计数据来看,50%的a
将与b[0]
位于同一缓存行中。然后,阅读b[0]
将加载a
中的a
部分。
如果你设法将类/结构对齐在128个字节上,你甚至可以保证b
和a
完全适合同一个缓存行。
现在,在您的示例中,您不使用16个浮点数而是使用1000个双精度浮点数。这是8000字节,比典型的缓存行大得多。 b[0]
的最后一些元素可能与{{1}}位于同一缓存行中,但效果会很小。