我的问题是为什么Visual Studio 2012编译器不会自动重新排序struct成员以获得最佳内存利用率?编译器似乎完全按照它们在结构定义中声明的顺序存储成员,并根据成员对齐需要一些空填充。似乎重新排序是一种比填充更有利于对齐成员的方法,只要有可能。是否有必要以声明顺序存储在内存中?
以下是相关细节;
我有一个结构,它代表一个大数组中的单个元素。该元素有许多成员,大约32位,大约64位。我调整了默认的struct member对齐以获得最佳性能。
我在调试模式下探索内存,发现内存中有很大一部分被浪费了。我跟踪了stuct成员如何在内存中对齐的问题。我知道32位成员必须在DWORD边界上对齐以获得最佳性能,并且看起来显然64位成员必须在QWORD边界上对齐(我原本认为DWORD边界已经足够了)
我能够通过更改在结构定义中列出成员的顺序来解决问题。我确保尽可能连续地放入2个32位成员,这样就不需要填充来启动QWORD边界上的下一个64位成员。
答案 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)
我怀疑这是重叠要求的交叉点。
如果编译器要重新排序数据成员以便更好地对齐和/或更紧密的打包,那么是否应该更改构造/销毁顺序的顺序?不,这会破坏许多依赖RAII的代码。但是现在构造期间的内存访问次数较少,这可能实际上是一种悲观,这取决于缓存行为,结构的大小以及构造这些结构的频率。
你可以说这些问题并不适用于POD结构,但是要求1和2说C ++编译器必须以与类相同的方式布局POD结构(反之亦然)。