【问题标题】:How does g++ align memory to make sure the alignment starts at the beginning of a cacheline?g++ 如何对齐内存以确保对齐从缓存行的开头开始?
【发布时间】:2018-07-08 16:23:39
【问题描述】:

我读到在 g++ 中我们可以做到:

struct foo {
    int a, b, c;
} __attribute__((__aligned__(64)));

因此 struct foo 的大小为 64 --- 这通常是缓存行的大小。当我们需要访问这个缓存行时,cpu可以一次性加载foo。

这是否意味着 CPU 可以在任何内存地址从 L2 加载 foo --- 不一定是 64 的整数?

假设 foo 在内存中的地址类似于 0xFFFF3,CPU 是否需要读取两条缓存线才能加载 foo?

谁能帮我澄清一下?

PS:我在 CentOS 64bit 上使用 g++ 6.2

【问题讨论】:

  • 我不是最新 C++ 语言标准细节方面的专家,但在过去,大多数编译器允许您对不同数据类型的对齐要求进行一些控制。原因是,在某些架构(例如 x86)上需要权衡:将结构打包得更紧密,程序运行速度会变慢,但数据占用的空间会更少。这是应允许应用程序开发人员做出的决定。
  • @jameslarge 为什么结构更紧密而程序运行更慢?我的想法正好相反:打包结构会减少缓存未命中,因此程序运行得更快。
  • 一个 64 位 x86-64 操作数可以位于任何字节地址,但如果地址不是,CPU 将不得不执行 两次 缓存操作来获取/存储它8 的倍数。如果在结构中混合不同大小的字段,则根据编译选项,编译器可能需要使用 padding(结构中未使用的空间)来使每个字段对齐其首选边界。如果您使用编译器选项,即“无填充”,那么您将获得更小的结构,但某些字段只能通过双重提取/存储来访问。对于某些架构,“无填充”甚至不是一个选项。

标签: c++ caching memory memory-management g++


【解决方案1】:

似乎有两个问题被掩盖为一个问题。我会尽量回答。

首先,对齐问题。一般来说,C++ 编译器对缓存行等一无所知(或假装一无所知)。相反,它只是确保这种类型的所有对象都与 64 字节边界对齐(前提是它们本身的大小小于或等于 64)。这是编译器工作完成的地方。

现在,我们需要关注 CPU 行为。由于缓存行大小为 64 字节,当对象在 64 字节边界上对齐时,我们确定在加载时整个对象都将在缓存中。因此,对对象成员的访问保证由 L1 缓存而不是更昂贵的方式(例如 L2)提供服务。

由于 L2 是一个更大的缓存,因此没有理由尝试对齐代码以遵守 L2 边界。

【讨论】:

  • 这是否意味着,如果我执行“foo* p = new foo();”,p 将始终指向一个 64 倍数的“圆形”内存地址?
  • @user152503 不在 C++17 之前的版本中。在 C++17 中,可以通过使用 align 参数来实现 operator new
【解决方案2】:

对齐与结构在内存中的起始地址有关。如果你的结构的大小小于或等于 64 字节,那么你是对的,你的结构将适合单行缓存,但如果结构大于 64 字节,那总是错误的。

对齐只与该类型的局部或全局变量的起始位置有关。当您编写试图释放处理器功能的算法时,最好优化对齐到 64 字节的数据访问,以便您可以处理 64 个连续字节而不会出现任何缓存故障。顺便说一句,当数据对齐到 64 字节时,编译器的优化器会创建更优化的代码。

快速测试是创建一个小程序,并生成对齐和不对齐的汇编代码,并比较你得到的汇编。 (例如访问局部向量的小循环),启用优化器(-O2 或 -O3)。

PS:使用堆的时候可以自己分配

struct foo *myFoo = (malloc(size(struct foo)+15) + 15) & (~ 0x0f);

【讨论】:

    猜你喜欢
    • 2010-12-11
    • 2023-01-18
    • 2021-09-14
    • 2021-12-02
    • 2011-06-25
    • 1970-01-01
    • 2021-07-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多