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

时间:2015-06-02 14:45:22

标签: c++ visual-studio-2012 compiler-optimization struct-member-alignment

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

以下是相关细节;

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

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

我能够通过更改在结构定义中列出成员的顺序来解决问题。我确保尽可能连续地放入2个32位成员,这样就不需要填充来启动QWORD边界上的下一个64位成员。

4 个答案:

答案 0 :(得分:3)

它是C ++标准,编译器无法修改字段的顺序,可能是因为程序员可能希望通过指向第一个字段的指针来访问字段。 如果您需要自己进行重新排序,请查看此article

第9.2.13节:

  

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

答案 1 :(得分:1)

标准布局结构或类中的数据必须具有某些布局保证。除其他事项外,如果有另一个标准布局结构或类是第一个的前缀,您必须能够将一个结构重新解释为另一个,并且公共前缀必须同意。

这基本上会强制标准布局结构的内存顺序按照您声明的顺序排列。

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

现在,在C ++中,非标准布局结构有一些自由。

[expr.rel] / 3 subpoint 3:

  

如果两个指针指向同一对象的不同非静态数据成员,或者指向这些成员的子对象,则递归地指向后面声明的成员的指针比较更大,前提是这两个成员具有相同的访问控制(第11条)如果他们的班级不是工会。

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

这意味着您可以知道某些程序员可能会使用&this->x大于或小于&this->y

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

根据我的经验,MSVC中元素之间的间距与普通旧数据结构中的间距匹配,除非通过虚拟游戏进行继承。布局兼容性(超出标准)对于稳定的ABI很重要,使用一个版本的编译器编译的代码优先用于另一个版本。打破这有成本。

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

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

答案 2 :(得分:0)

如果没有#pragma s,内存不会被C ++打包,也不会重新排序,因为语言保证了与代码一致的布局。想象一下可能造成的破坏 - 将结构映射到文件(内存映射文件)或硬件永远不会工作。

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

答案 3 :(得分:0)

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

  1. C和C ++中相同POD结构的布局应该是二进制兼容的。 (这是否是标准所要求的,我不知道,但大多数编译器供应商可能会优先考虑它,因为许多现有代码依赖于它。)
  2. 除了默认的可见性之外,C ++中的结构和类实际上是相同的。
  3. 类的数据成员按其声明的顺序构造,并以相反的顺序销毁。
  4. 如果编译器要重新排序数据成员以便更好地对齐和/或更紧密的打包,那么是否应该更改构造/销毁顺序的顺序?不,这会破坏许多依赖RAII的代码。但是现在构造期间的内存访问次数较少,这可能实际上是一种悲观,这取决于缓存行为,结构的大小以及构造这些结构的频率。

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