为什么 Visual Studio 没有优化结构以获得最佳内存使用率?
How come Visual Studio does not optimize structs for best memory usage?
我的问题是为什么 Visual Studio 2012 编译器不自动重新排序结构成员以获得最佳内存利用率?编译器似乎完全按照它们在结构定义中声明的顺序存储成员,并根据成员对齐的需要进行一些空填充。只要有可能,重新排序似乎是比填充更可取的对齐成员的方法。是否有必须按声明顺序存储在内存中的原因?
相关细节如下;
我有一个结构,它代表一个大数组中的单个元素。该元素有许多成员,有的是 32 位的,有的是 64 位的。我已调整默认结构成员对齐以获得最佳性能。
我在调试模式下探索内存,发现浪费了很大一部分内存。我跟踪了结构成员如何在内存中对齐的问题。我知道 32 位成员必须在 DWORD 边界上对齐以获得最佳性能,而且显然 64 位成员必须在 QWORD 边界上对齐(我原以为 DWORD 边界就足够了)
我能够通过更改在结构定义中列出成员的顺序来解决问题。我确保尽可能依次放置 2 个 32 位成员,这样就不需要填充来启动 QWORD 边界上的下一个 64 位成员。
这是 C++ 标准,编译器不能修改字段的顺序,可能是因为程序员可能希望通过指向第一个字段的指针访问字段。
如果您需要自己重新排序,请查看此 article
第 9.2.13 节:
Nonstatic data members of a (non-union) class with the same access
control (Clause 11) are allocated so that later members have higher
addresses within a class object. The order of allocation of non-static
data members with different access control is unspecified (Clause 11).
Implementation alignment requirements might cause two adjacent members
not to be allocated immediately after each other; so might
requirements for space for managing virtual functions (10.3) and
virtual base classes (10.1).
如果没有 #pragma
s,内存不会被 C++ 打包并且不会重新排序,因为该语言保证布局与代码一致。想象一下会造成的破坏——将结构映射到文件(内存映射文件)或硬件将永远无法工作。
为了感受 class 或结构的布局,Visual C++ 提供了一个未记录的命令行参数 /d1reportSingleClassLayout
,它将为您画出您的内存布局的 ASCII 艺术图class/struct,包括所有成员、基成员和vtable。例如,如果您有一个名为 foo
的 class,请将 /d1reportSingleClassLayoutfoo
添加到您的编译器命令行。
标准布局结构中的数据或class必须做出一定的布局保证。除其他事项外,如果有另一个标准布局结构或 class 作为第一个的前缀,您必须能够将一个结构重新解释为另一个结构,并且通用前缀必须一致。
这基本上强制标准布局结构的内存顺序与您声明它们的顺序一致。
这与 C 在结构布局方面的要求类似,如 here 所述。
现在,在 C++ 中,为非标准布局结构提供了一些自由。
[expr.rel]/3 子点 3:
If two pointers point to different non-static data members of the same object, or to subobjects of such members, recursively, the pointer to the later declared member compares greater provided the two members have the same access control (Clause 11) and provided their class is not a union.
必须在 public/private/protected 访问控制域中维护元素的顺序。 Space 元素之间几乎可以任意添加。
这意味着你可以知道&this->x
大于或小于&this->y
,有些程序员可能会使用
根据 as-if 规则,如果没有人获取此类数据的地址,编译器可以对它们重新排序。这在通常的编译模型中很难证明。
根据我的经验,MSVC 中元素之间的间距与普通旧数据结构中的间距相匹配,除非继承与虚拟玩游戏。布局兼容性(超出标准)对于稳定的 ABI 很重要,使用一种版本的编译器编译的代码更适合在另一种版本的编译器中运行。打破是有代价的。
C++ 程序员可以根据需要重新排序数据结构,visual studio 提供了 #pragma
来更改结构打包规则,因此如果您真的需要最后一点性能,您可以得到它。
您甚至可以编写类似 tuple
的数据结构来保证在需要时进行最佳打包。 (我不会依赖 std::tuple
,因为它没有包装保证)
我怀疑这是重叠要求的交叉点。
- C 和 C++ 中相同 POD 结构的布局应该是二进制兼容的。 (这是否是标准所要求的,我不知道,但大多数编译器供应商可能会优先考虑它,因为许多现有代码都依赖于它。)
- 除了默认可见性之外,C++ 中的结构和 classes 实际上是相同的东西。
- class 的数据成员按声明的顺序构造,并按相反的顺序销毁。
如果编译器要重新排序数据成员以更好地对齐 and/or 更紧密的包装,是否应该更改 construction/destruction 顺序?不,那会破坏很多依赖 RAII 的代码。但是现在构造过程中的内存访问不那么有序了,这实际上可能是一种悲观化,这取决于缓存行为、结构的大小以及构造这些结构的频率。
您可能会争辩说这些问题不适用于 POD 结构,但要求 1 和 2 规定 C++ 编译器必须以与 classes 相同的方式布置 POD 结构(反之亦然)。
我的问题是为什么 Visual Studio 2012 编译器不自动重新排序结构成员以获得最佳内存利用率?编译器似乎完全按照它们在结构定义中声明的顺序存储成员,并根据成员对齐的需要进行一些空填充。只要有可能,重新排序似乎是比填充更可取的对齐成员的方法。是否有必须按声明顺序存储在内存中的原因?
相关细节如下;
我有一个结构,它代表一个大数组中的单个元素。该元素有许多成员,有的是 32 位的,有的是 64 位的。我已调整默认结构成员对齐以获得最佳性能。
我在调试模式下探索内存,发现浪费了很大一部分内存。我跟踪了结构成员如何在内存中对齐的问题。我知道 32 位成员必须在 DWORD 边界上对齐以获得最佳性能,而且显然 64 位成员必须在 QWORD 边界上对齐(我原以为 DWORD 边界就足够了)
我能够通过更改在结构定义中列出成员的顺序来解决问题。我确保尽可能依次放置 2 个 32 位成员,这样就不需要填充来启动 QWORD 边界上的下一个 64 位成员。
这是 C++ 标准,编译器不能修改字段的顺序,可能是因为程序员可能希望通过指向第一个字段的指针访问字段。 如果您需要自己重新排序,请查看此 article
第 9.2.13 节:
Nonstatic data members of a (non-union) class with the same access control (Clause 11) are allocated so that later members have higher addresses within a class object. The order of allocation of non-static data members with different access control is unspecified (Clause 11). Implementation alignment requirements might cause two adjacent members not to be allocated immediately after each other; so might requirements for space for managing virtual functions (10.3) and virtual base classes (10.1).
如果没有 #pragma
s,内存不会被 C++ 打包并且不会重新排序,因为该语言保证布局与代码一致。想象一下会造成的破坏——将结构映射到文件(内存映射文件)或硬件将永远无法工作。
为了感受 class 或结构的布局,Visual C++ 提供了一个未记录的命令行参数 /d1reportSingleClassLayout
,它将为您画出您的内存布局的 ASCII 艺术图class/struct,包括所有成员、基成员和vtable。例如,如果您有一个名为 foo
的 class,请将 /d1reportSingleClassLayoutfoo
添加到您的编译器命令行。
标准布局结构中的数据或class必须做出一定的布局保证。除其他事项外,如果有另一个标准布局结构或 class 作为第一个的前缀,您必须能够将一个结构重新解释为另一个结构,并且通用前缀必须一致。
这基本上强制标准布局结构的内存顺序与您声明它们的顺序一致。
这与 C 在结构布局方面的要求类似,如 here 所述。
现在,在 C++ 中,为非标准布局结构提供了一些自由。
[expr.rel]/3 子点 3:
If two pointers point to different non-static data members of the same object, or to subobjects of such members, recursively, the pointer to the later declared member compares greater provided the two members have the same access control (Clause 11) and provided their class is not a union.
必须在 public/private/protected 访问控制域中维护元素的顺序。 Space 元素之间几乎可以任意添加。
这意味着你可以知道&this->x
大于或小于&this->y
,有些程序员可能会使用
根据 as-if 规则,如果没有人获取此类数据的地址,编译器可以对它们重新排序。这在通常的编译模型中很难证明。
根据我的经验,MSVC 中元素之间的间距与普通旧数据结构中的间距相匹配,除非继承与虚拟玩游戏。布局兼容性(超出标准)对于稳定的 ABI 很重要,使用一种版本的编译器编译的代码更适合在另一种版本的编译器中运行。打破是有代价的。
C++ 程序员可以根据需要重新排序数据结构,visual studio 提供了 #pragma
来更改结构打包规则,因此如果您真的需要最后一点性能,您可以得到它。
您甚至可以编写类似 tuple
的数据结构来保证在需要时进行最佳打包。 (我不会依赖 std::tuple
,因为它没有包装保证)
我怀疑这是重叠要求的交叉点。
- C 和 C++ 中相同 POD 结构的布局应该是二进制兼容的。 (这是否是标准所要求的,我不知道,但大多数编译器供应商可能会优先考虑它,因为许多现有代码都依赖于它。)
- 除了默认可见性之外,C++ 中的结构和 classes 实际上是相同的东西。
- class 的数据成员按声明的顺序构造,并按相反的顺序销毁。
如果编译器要重新排序数据成员以更好地对齐 and/or 更紧密的包装,是否应该更改 construction/destruction 顺序?不,那会破坏很多依赖 RAII 的代码。但是现在构造过程中的内存访问不那么有序了,这实际上可能是一种悲观化,这取决于缓存行为、结构的大小以及构造这些结构的频率。
您可能会争辩说这些问题不适用于 POD 结构,但要求 1 和 2 规定 C++ 编译器必须以与 classes 相同的方式布置 POD 结构(反之亦然)。