为什么Visual Studio没有优化结构体以获得最佳内存使用?

How come Visual Studio does not optimize structs for best memory usage?

本文关键字:最佳 内存 Studio Visual 结构体 优化 为什么      更新时间:2023-10-16

我的问题是为什么Visual Studio 2012编译器不自动重新排序结构成员以获得最佳内存利用率?编译器似乎完全按照它们在结构定义中声明的顺序存储成员,并根据成员对齐的需要使用一些空填充。在任何可能的情况下,重新排序似乎是一种比填充更理想的对齐成员的方式。是否有理由必须按照声明顺序存储在内存中?

相关细节如下;

我有一个结构体,它代表一个大数组中的单个元素。元素有许多成员,有的32位,有的64位。我设置了默认的结构成员对齐以获得最佳性能。

我在调试模式下探索内存,发现很大一部分内存被浪费了。我跟踪问题到结构体成员如何在内存中对齐。我知道32位成员必须在DWORD边界上对齐以获得最佳性能,并且显然64位成员必须在QWORD边界上对齐(我本以为DWORD边界已经足够了)

我可以通过改变在结构定义中列出成员的顺序来解决这个问题。我确保在可能的情况下依次放置2个32位成员,以便在QWORD边界上启动下一个64位成员时不需要填充。

这是c++标准,编译器不能修改字段的顺序,可能是因为程序员可能想通过指向第一个字段的指针来访问字段。如果您需要自己重新排序,请查看这篇文章

Section 9.2.13:

具有相同访问权限的(非联合)类的非静态数据成员控制权(第11条)分配,以便后来者拥有更高的控制权类对象中的地址。分配顺序是非静态的具有不同访问控制的数据成员未指定(第11条)。实现一致性需求可能导致两个相邻的成员一个接一个地分配;所以可能管理虚拟功能的空间要求(10.3)和虚基类(10.1).

在标准布局结构或类中的数据必须有一定的布局保证。除此之外,如果有另一个标准布局结构或类是第一个结构的前缀,您必须能够将一个结构重新解释为另一个结构,并且公共前缀必须一致。

这基本上强制标准布局结构的内存顺序按照你声明它们的顺序。

这与C在结构布局方面的要求类似,如下所述。

现在,在c++中,对于非标准布局结构有一定的自由度。

[expr。Rel]/3 subpoint 3:

如果两个指针递归地指向同一对象的不同非静态数据成员,或者指向此类成员的子对象,如果两个成员具有相同的访问控制(第11条)并且它们的类不是联合,则指向后声明成员的指针比较大。

元素的顺序必须在公共/私有/受保护的访问控制域中保持。元素间的空格几乎可以任意添加。

这意味着您可以知道&this->x大于或小于&this->y,这是一些程序员可能使用的。

在as-if规则下,如果没有人获取这些数据的地址,编译器可以对它们重新排序。这在通常的编译模型中很难证明。

MSVC中元素之间的间距与普通旧数据结构中的间距相匹配,以我的经验,禁止与虚拟玩游戏的继承。布局兼容性(超出标准)对于稳定的ABI非常重要,使用一个版本的编译器编译的代码更倾向于在另一个版本的编译器中工作。打破它是要付出代价的。

c++程序员可以根据需要重新排序数据结构,visual studio提供了#pragma s来改变结构打包规则,所以如果你真的需要最后一点性能,你可以得到它。

如果需要的话,您甚至可以编写一个类似tuple的数据结构来保证最佳打包。(我不会依赖std::tuple,因为它没有包装保证)

没有#pragma s,内存不会被c++打包,也不会被重新排序,因为语言保证布局与代码一致。想象一下这会造成的混乱——将结构映射到文件(内存映射文件)或硬件将永远无法工作。

为了感受类或结构的布局,Visual c++提供了一个未记录的命令行参数/d1reportSingleClassLayout,它将为您绘制类/结构的内存布局的ascii艺术图,包括所有成员,基成员和虚函数表。例如,如果您有一个名为foo的类,那么将/d1reportSingleClassLayoutfoo添加到编译器命令行中。

我怀疑这是在重叠需求的交叉点。

  1. 同一个POD结构体在C和c++中的布局应该是二进制兼容的。(这是否是标准所要求的,我不知道,但大多数编译器供应商可能优先考虑它,因为许多现有代码依赖于它。)
  2. c++中的结构和类实际上是一样的,除了默认的可见性。
  3. 类的数据成员按声明的顺序构造,按相反的顺序销毁。

如果编译器为了更好地对齐和/或更紧密地打包而重新排序数据成员,这应该改变构造/销毁顺序吗?不,那会破坏很多依赖RAII的代码。但是现在构造过程中的内存访问不那么有序了,这实际上可能是一种悲观,这取决于缓存行为、结构的大小和这些结构的构造频率。

您可能认为这些问题不适用于POD结构,但是需求1和2说,c++编译器必须以与类相同的方式布局POD结构(反之亦然)。