内存对齐优化不仅性能,而且内存大小

Memory alignment optimization of not only performance but also the memory size

本文关键字:内存 性能 对齐 优化      更新时间:2023-10-16

我们知道,当您这样定义A时,A的大小存在差异:

class A 
{
  short a;
  double b;
  short c;
};

或者像这个

class A 
{
  short a;
  short c;
  double b;
};

我假设我们是为32位操作系统编译的,并且我们已经告诉编译器要对齐到32位。

编译器真的很难通过重新排序定义来获得最小的大小,同时实现相同的性能吗?

这是非常困难的。特别需要一个结构来按照与结构定义完全相同的顺序对字段进行排序。

这个要求可能是对Pascal没有这样的要求并导致令人惊讶的结果的反应。

无论如何,并不是所有的CPU架构都需要对齐或填充。在大多数情况下,它会导致轻微的性能损失。在现代处理器时代,由于CPU管道的其他方面,内存获取中额外的一两个周期很可能会消失。

这对编译器来说并不困难,它被标准禁止(只有一个例外):第9.2.12节:

声明的(非并集)类的非静态数据成员没有分配介入访问说明符,以便以后的成员类对象中的较高地址。的分配顺序由访问说明符分隔的非静态数据成员是未指定的

例外的是,具有不同访问修饰符的成员可以被重新排序,因此:

class A
{
public:
  int a;
  int b;
private:
  int c;
 int d;
{';

a和b不能被重新排序。c和d,不能重新排序,但(a和b)可以通过(c和d)重新排序

由于标准的要求,编译器无法重新排序(大致为:唯一、增量地址,以及按访问修饰符分组的C++类)。

这就是为什么重新排序需要手工完成的原因。一般来说,你在这里很幸运:更好地对齐现有的数据类型意味着更小的大小和更好的性能,这里没有冲突。

然而,有时较大的数据(元素)大小意味着更简单的指令。例如,使用位字段来减少几个字节意味着更复杂的代码,代码大小和数据大小之间存在权衡;将内部循环增加500字节以减少2k个数据对于代码的优化和内存局部性来说可能是灾难性的。

[edit]当结构元素顺序不理想并且可以改进时,PVS Studio等工具可能会发出警告


[edit2]至于"为什么存在这些规则"

tl;dr:这很有趣,但并不重要,只是为了好奇。

首先(从另一个SO答案中无情地复制)标准的相关部分:

在结构对象中,非位字段成员和位字段所在的单元的地址按声明顺序增加。指向结构对象的指针(经过适当转换)指向其初始成员(或者,如果该成员是位字段,则指向其所在的单元),反之亦然。结构对象中可能存在未命名的填充,但不在其开头。

一个指向标准布局结构对象的指针,使用interpret_cast进行适当转换,指向其初始成员(或者,如果该成员是位字段,则指向其所在的单元),反之亦然。[注意:因此,在标准布局结构对象中可能会有未命名的填充,但不是在其开头,这是实现适当对齐所必需的。--结束注释]

(注意,这与C有关,C++有点复杂。)

该标准很少说明原因,大多数都是"有根据的猜测":

不同地址是本标准其他关注点的要求。

使指向结构类型转换的指针等效于指向第一个成员的指针当然是由于现有的编程实践。(通过将"基础结构"嵌套为"派生结构"的第一个成员,它允许C中的"数据多态性")

保留程序员指定的顺序

"一般的猜测"是不破坏手工优化的数据位置。将最常访问的成员放在顶部可以提高结构中的位置性(允许更好的缓存或更短的寻址指令)。如果编译器重新排序,这些优化可能会因为结构总大小的相对较小的增益而丢失。

C++需要按访问说明符分组:(即"所有公共变量一起,所有受保护的变量一起,全部私有变量一起"):我从来没有找到原因(我必须说,这有点令人惊讶)。我可以想象(也许)目的是允许编译器实现利用硬件访问控制(这段代码可能无法访问那块内存")。OTOH我知道没有任何体系结构可以允许这种精细的控制级别,我从未见过访问说明符被认为是一种安全机制。