访问一个结构体成员会将整个结构体都读入缓存吗?

7
我一直在阅读 Ulrich Drepper 的 "程序员应该了解的关于内存的知识",在 3.3.2 缓存效应的测量(页面中部)一节中,给我的印象是访问结构体的任何成员都会导致整个结构体被拉入 CPU 缓存。

这是正确的吗?如果是这样,硬件如何知道这些结构体的布局?或者编译器生成的代码是否会强制加载整个结构体?

还是使用较大结构体时的减速主要是由于结构体分散在更多内存页上引起的 TLB 未命中?

Drepper 使用的示例结构体是:

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

NPAD等于0、7、15或31时,sizeof(l)由此确定,导致结构体之间相隔0、56、120和248字节,并假设缓存行为64字节和4k页面。

即使实际上只访问指针,但随着结构体的增长,仅仅迭代遍历链表也会显著变慢。

6个回答

13
硬件完全不知道结构体。但是确实如此,硬件在缓存中加载一些你实际访问的字节周围的字节。这是因为缓存行有一个大小。它不逐字节访问,而是以16字节为单位进行工作。
你必须小心地安排结构体成员的顺序,以使经常使用的成员彼此靠近。例如,如果你有以下结构体:
struct S {
  int foo;
  char name[64];
  int bar;
};

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

struct S {
  int foo;
  int bar;
  char name[64];
};
当你使用foo时,硬件会在缓存中加载围绕foo的字节。当你使用bar时,因为bar包含在围绕foo的字节中,所以bar已经在缓存中了。CPU不必等待bar出现在缓存中。 答案是:访问单个结构体成员不会将整个结构体放入缓存中,而是将结构体的其他成员放入缓存中。

9
硬件并不知道结构体的布局,只是将访问成员周围的一些字节加载到缓存中。是的,较大的结构体会导致速度变慢,因为它们会跨越更多的缓存行。

这是完全正确的。这里的花哨概念是引用局部性。 - jason
当你说“跨越更多高速缓存行”时,你的意思是慢速的部分来自于预取周围结构体未使用部分,除了TLB缺失。 - Robert S. Barnes
1
@Robert:我认为它可以有两种不同的应用方式:1. 单个结构体太大,无法舒适地放入单个缓存页中。如果你“到处摸它”(听起来很脏),它可能会导致多个页面获取。2. 对于较大的结构体,您只能在任何单个缓存页读取时将较少的结构体放入缓存中,从而增加下一个您想要的结构体不在缓存中的概率。这里的一个基本问题是引用局部性。如果您在内存中跳跃,您将有更多的缓存未命中。了解您的访问模式并相应设计。 - Peter Rowell

3

访问结构体成员不会比访问内存中的其他区域有更多的性能损失。事实上,如果您在相同的区域访问多个结构体成员,可能会有性能提升,因为第一次访问可能会缓存其他成员。


1
通常情况下,L1缓存使用虚拟地址,如果您访问struct的成员,则会将特定数量的字节放入缓存(一个缓存行,大小通常在8到512字节之间)。由于所有struct成员在内存中侧边对齐,整个结构体进入缓存的机会相当大(取决于sizeof(struct your_struct))...

1

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

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

此外,一些CPU执行推测预取;这意味着如果您按顺序访问缓存行5和6,则会尝试在不要求您的情况下加载缓存行7。


1
“只是遍历链表,随着结构体的增长,速度会显著变慢,尽管实际上除了指针之外没有访问其他内容。”
“当NPAD = 0时,每个缓存行包含8个列表节点,因此您可以看到这是最快的。”
“当NPAD = 7、15、31时,每个列表节点只需要加载一个缓存行,您可能会期望它们的速度都相同-每个节点一个缓存未命中。但是现代内存管理器将进行推测性缓存。如果它有多余的容量(因为使用现代内存可以并行执行多个读取操作),那么它将开始加载靠近您正在使用的内存的内存。虽然它是一个链接列表,但如果您以任何明显的方式构建它,则有很大的机会您正在按顺序访问内存。因此,列表节点在内存中越接近,缓存就越有可能成功地拥有您所需的内容。”
在最糟糕的情况下,当您使用内存时,它被从交换中拉入,您的程序将受到磁盘I/O的限制。可能您通过列表的进度速度完全取决于每个页面有多少节点,并且您可能会看到所花费的时间与节点大小成正比,最高可达4k。虽然我没有尝试过,但操作系统会像MMU一样聪明地处理交换,因此并不一定那么简单。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接