我一直在阅读Ulrich Drepper,“What every programmer should know about memory”,在3.3.2 Measurements of Cache Effects部分(页面中间),它给我的印象是访问结构的任何成员会导致整个结构得到拉入CPU缓存。
这是对的吗?如果是这样,硬件如何知道这些结构的布局?或者编译器生成的代码是否以某种方式强制加载整个结构?
或者使用较大结构体的速度是否因为结构体分布在更多内存页面而导致的TLB未命中而导致减速?
Drepper使用的示例结构是:
struct l {
struct l *n;
long int pad[NPAD];
};
sizeof(l)
由NPAD
确定等于0,7,15或31,导致结构相隔0,56,120和248字节并假设高速缓存行为64字节和4k页。
随着结构的增长,迭代遍历链表变得非常慢,即使实际上没有其他指针被访问。
答案 0 :(得分:8)
硬件不知道结构的布局,只是将访问过的成员周围的一些字节加载到缓存中。是的,较大结构的减速是因为它们将分布在更多的缓存行中。
答案 1 :(得分:8)
硬件根本不了解结构。但确实,高速缓存中的硬件负载在您实际访问的字节周围的某些字节。这是因为缓存行具有大小。它不能用于逐字节访问,而是在例如一次16字节大小。
在排序结构的成员时必须小心,以便经常使用的成员彼此接近。例如,如果您有以下结构:
struct S {
int foo;
char name[64];
int bar;
};
如果经常使用成员变量foo和bar,硬件将在缓存中加载foo周围的字节,当你访问bar时,它必须加载bar周围的字节。即使foo和bar附近的这些字节从未使用过。现在按如下方式重写结构:
struct S {
int foo;
int bar;
char name[64];
};
当你使用foo时,硬件将加载缓存foo周围的字节。当你使用bar时,bar已经在缓存中,因为bar包含在foo周围的字节中。 CPU不必等待bar进入缓存。
答案是 :访问单个结构成员不会将整个结构体拉入缓存中,而是将结构体的其他成员拉入缓存中。
答案 2 :(得分:3)
访问struct成员不会比访问内存中的任何其他区域造成性能损失。事实上,如果您访问同一区域中的多个struct成员,可能会有性能提升,因为第一次访问可能会缓存其他成员。
答案 3 :(得分:1)
通常,L1缓存使用virtual addresses,如果您访问struct
的成员,则缓存中会有一个特定数量的字节(一个cache line,大小通常在8和之间512字节)。由于所有struct
成员在内存中并排排列,因此整个结构进入缓存的可能性有些大(取决于sizeof(struct your_struct)
)...
答案 4 :(得分:1)
虽然CPU可以愉快地处理小到一个字节的加载和存储,但缓存只处理“高速缓存行”大小的数据。在计算机体系结构教科书中,这也称为“块大小”。
在大多数系统上,这是32或64字节。它可以从一个CPU到另一个CPU不同,甚至有时从一个缓存级别到下一个缓存级别。
此外,一些CPU执行推测预取;这意味着如果按顺序访问高速缓存行5和6,它将尝试加载高速缓存行7而不需要它。
答案 5 :(得分:1)
“随着结构的增长,只是迭代遍历链表会变得非常慢,即使实际上没有其他指针被访问。”
NPAD = 0时,每个缓存行包含8个列表节点,因此您可以看到为什么这个最快。
对于NPAD = 7,15,31,每个列表节点只需要加载一个缓存行,并且您可能希望它们都具有相同的速度 - 每个节点一个缓存未命中。但现代内存管理器将进行推测性缓存。如果它有备用容量(它可能会有,因为现代内存它可以与主内存并行执行多次读取),然后它将开始加载靠近你正在使用的内存的内存。虽然它是一个链表,如果你以任何一种显而易见的方式构造它,那么你很有可能按顺序访问内存。因此,列表节点在内存中的距离越近,就已经拥有所需内容而言,缓存可能就越成功。
在最糟糕的情况下,当你使用它时你的内存从swap中被拉入,你的程序将受到磁盘I / O的限制。您通过列表的进度可能完全取决于每页有多少个节点,您可能会看到时间与节点的大小成正比,最多可达4k。不过我还没有尝试过,操作系统会很聪明,因为MMU对主存很聪明,所以它不一定那么简单。