双重对齐
根据这篇文章的讨论,我理解结构成员对齐的主要原因是性能(以及一些架构限制)。
如果我们将在编译32位x86时调查Microsoft(Visual C ++),Borland / CodeGear(C ++ – Builder),Digital Mars(DMC)和GNU(GCC): int
的对齐是4个字节,如果int
未对齐,可能会发生2行存储体的读取。
我的问题是为什么不将double
设为4字节对齐呢? 4字节对齐double
也会导致2行内存库读取….
例如,在下面的示例中,由于double
是8对齐的,因此结构的实际大小将是sizeof(char) + (alignment for double padding) + sizeof(int) = 20 bytes
。
typedef struct structc_tag{ char c; double d; int s; } structc_t;
谢谢
延伸评论:
根据海湾合作委员会关于-malign-double
文件:
在
double
字边界上对齐double
变量会产生在奔腾上运行得更快的代码,代价是更多的内存。在x86-64上,默认情况下启用
-malign-double
。警告:如果使用
-malign-double
开关,则包含上述类型的结构的排列方式与386的已发布应用程序二进制接口规范的排列方式不同,并且与未使用该开关编译的代码中的结构不兼容。
这里的一个词是指32位的i386字。
即使在32位模式下,Windows也使用64位对齐值,而SysV i386 ABI符合Unices使用32位对齐。 32位Windows API Win32来自Windows NT 3.1,与当前一代Windows版本不同,它针对的是英特尔i386,Alpha,MIPS甚至是不起眼的英特尔i860。 由于像Alpha和MIPS这样的原生RISC系统需要将double
值进行64位对齐(否则会发生硬件故障),因此可移植性可能是Win32 i386 ABI中64位对齐背后的基本原理。
64位x86系统,也称为AMD64或x86-64或x64,需要double
值才能进行64位对齐,否则会出现未对准故障,并且硬件会进行昂贵的“修复”,这会显着降低内存访问速度。 这就是为什么在所有现代x86-64 ABI(SysV和Win32)中, double
值都是64位对齐的原因。
大多数编译器会自动将数据值与平台的字大小或数据类型的大小对齐,以较小者为准。 绝大多数消费者和企业处理器使用32位字长。 (即使64位系统通常使用32位作为本机字大小)
因此,结构中成员的排序可能会浪费一些内存。 在你的具体情况下,你很好。 我将在评论中添加已用内存的实际占用空间:
typedef struct structc_tag{ char c; // 1 byte // 3 bytes (padding) double d; // 8 bytes int s; // 4 bytes } structc_t; // total: 16 bytes
此规则也适用于结构,因此即使您重新排列它们以使最小字段最后,您仍将具有相同大小的结构(16字节)。
typedef struct structc_tag{ double d; // 8 bytes int s; // 4 bytes char c; // 1 byte // 3 bytes (padding) } structc_t; // total: 16 bytes
如果要声明更多小于4个字节的字段,如果按大小将它们组合在一起,则可以看到一些内存减少。 例如:
typedef struct structc_tag{ double d1; // 8 bytes double d2; // 8 bytes double d3; // 8 bytes int s1; // 4 bytes int s2; // 4 bytes int s3; // 4 bytes short s4; // 2 bytes short s5; // 2 bytes short s6; // 2 bytes char c1; // 1 byte char c2; // 1 byte char c3; // 1 byte // 3 bytes (padding) } structc_t; // total: 48 bytes
声明一个愚蠢的结构可能会浪费大量的内存,除非编译器重新排序你的元素(一般情况下,它不会被明确告知)
typedef struct structc_tag{ int s1; // 4 bytes char c1; // 1 byte // 3 bytes (padding) int s2; // 4 bytes char c2; // 1 byte // 3 bytes (padding) int s3; // 4 bytes char c3; // 1 byte // 3 bytes (padding) } structc_t; // total: 24 bytes // (9 bytes wasted, or 38%) // (optimal size: 16 bytes (1 byte wasted))
双打大于32位,因此根据第一节中的规则,32位对齐。 有人提到了一个更改对齐的编译器选项,并且默认编译器选项在32位和64位系统之间是不同的,这也是有效的。 所以关于双打的真正答案是它取决于平台和编译器。
内存性能由单词控制:从内存加载发生在依赖于数据放置的阶段。 如果数据覆盖一个单词(即单词对齐),则只需加载该单词。 如果未正确对齐(即0x2处的int),则处理器必须加载2个字才能正确读取其值。 这同样适用于双打,通常占用2个字,但如果未对齐,则占用3.在64位系统上,可能有64位数量的本机加载,它们在32位系统上的行为类似于32位,如果正确对齐,它们可以一次装载,但除此之外,它们需要2次。
首先,它是强加对齐要求的架构,有些会容忍未对齐的内存访问,有些则不会。
让我们以x86-32bit
windows平台为例,在这个平台中, int
和long
的对齐要求分别是4 bytes
和8 bytes
。
很明显为什么int
对齐要求是4 bytes
,只是为了使cpu只能通过一次访问来读取它。
doulbe
的对齐要求是8 bytes
而不是4 bytes
的原因是因为如果它是4 bytes
那么考虑如果这个双重位于地址60
并且高速缓存行大小是64bits
将会发生什么,在这种情况下处理器需要从内存加载2个缓存行到缓存,但如果这个double
对齐,则不会发生这种情况,因为在这种情况下, double
将始终是一个缓存行的一部分而不是在两个之间分配。
...58 59|60 61 62 63 64 65 66 67|68 69 70 71... - - - - - - - - - - - - - - - - - ----------+ + + + . . + + + +-------------- | . . | ----------+ + + + . . + + + +-------------- . . Cache Line 1 . . Cache Line 2 - - - - - - - - - - - - - - - - -
问题是CPU架构方面的高度平台特定性。 例如,在对非4字节对齐的地址进行操作而给予惩罚的体系结构中,将变量(实际上是它们的地址)与4个字节相对应可以避免这种惩罚。
编译器非常适合这样的东西,特别是当你为它们提供目标CPU架构以进行优化时,它们可以为你做大部分工作,以及许多其他优化。 例如,看一下GCC的-march
标志,它可以让你以CPU架构为目标。