访问单个结构成员是否将整个结构拖入Cache中?

我一直在阅读Ulrich Drepper的“ 每个程序员应该知道什么内存 ”和第3.3.2节“caching效果的度量” (页面中间部分),这给我的印象是访问结构的任何成员会导致整个结构被拉进CPUcaching。

它是否正确? 如果是这样,硬件如何知道这些结构的布局? 还是由编译器生成的代码以某种方式强制整个结构被加载?

或者是由于结构分布在更多内存页面导致的TLB未命中,使用更大结构的速度减慢?

Drepper使用的示例结构是:

struct l { struct l *n; long int pad[NPAD]; }; 

其中, sizeof(l)NPAD确定等于NPAD或31,得到NPAD和248字节的结构,并假定高速caching行是64字节和4k页。

只要迭代链表,就会随着结构的增长而显着变慢,尽pipe除了指针之外没有别的东西被实际访问。

硬件完全不知道结构。 但是,确实缓存中的硬件负载在实际访问的字节周围有一些字节。 这是因为缓存行有一个大小。 它不能以逐字节访问的方式工作,而是一次处理16个字节的数据。

在订购结构的成员时必须小心,以便经常使用的成员彼此接近。 例如,如果你有以下的结构:

 struct S { int foo; char name[64]; int bar; }; 

如果成员变量foo和bar经常被使用,硬件将会缓存foo周围的字节,当你访问bar时,它将不得不加载围绕bar的字节。 即使foo周围的这些字节从不使用。 现在重写你的结构如下:

 struct S { int foo; int bar; char name[64]; }; 

当你使用foo的时候,硬件会在缓存中加载foo的字节。 当你使用bar时,bar将已经在缓存中,因为bar包含在foo的字节中。 CPU不必等待栏位在缓存中。

答案是 :访问一个单一的结构成员不会拉高速缓存中的整个结构,但将结构的其他成员拉入缓存。

硬件不知道结构的布局,只是将被访问的成员周围的一些字节加载到缓存中。 是的,从较大的结构放缓是因为他们将分布在更多的缓存行。

访问一个struct成员不会比访问内存中的任何其他区域更多的性能损失。 实际上,如果您访问同一区域中的多个结构成员,可能会提高性能,因为其他成员可能会被第一次访问缓存。

通常,L1高速缓存使用虚拟地址 ,如果访问struct成员,则特定数量的字节会进入高速缓存(一个高速缓存行 ,大小通常在8到512字节之间)。 由于所有struct成员在内存中并排排列,因此整个结构进入缓存的机会有点大(取决于sizeof(struct your_struct) )…

虽然CPU可以愉快地处理小至一个字节的加载和存储,但是缓存只处理“缓存行”大小的数据。 在计算机体系结构的教科书中,这也被称为“块大小”。

在大多数系统上,这是32或64字节。 它可能不同于一个CPU,甚至有时从一个高速缓存到另一个高速缓存。

此外,一些CPU执行预测性预取; 这意味着如果您按顺序访问cacheline 5和6,它将尝试加载cacheline 7而不要求它。

“只要迭代链表,就会随着结构的增长而慢得多,即使指针实际上没有被访问。

NPAD = 0,每个缓存行包含8个列表节点,所以你可以看到为什么这是最快的。

在NPAD = 7,15,31的情况下,每个列表节点只需要加载一个缓存行,并且您可能期望它们都是相同的速度 – 每个节点只有一个缓存未命中。 但现代内存管理员将会进行投机缓存。 如果它有多余的容量(这可能是因为使用现代内存,它可以并行执行多次读取),那么它将开始加载内存到您正在使用的内存附近。 虽然它是一个链表,但如果你用任何明显的方式构造它,那么你很有可能依次访问内存。 因此,列表节点越接近内存,缓存越有可能成为您已经拥有的内容。

在最糟糕的情况下,当你使用它的时候你的内存被从交换中拉出来,你的程序将受到磁盘I / O的限制。 通过列表进度的速度可能完全取决于每个页面有多少个节点,并且您可能会看到花费的时间与节点的大小成正比,最高可达4k。 但是我还没有尝试过,操作系统会随着交换机巧妙地进行交换,就像MMU对主内存很聪明一样,所以不一定那么简单。