C++ 内存对齐——我们应该关心吗?
C++ Memory alignment - should we care?
考虑在具有以下类型对齐的 x64 位 Windows 操作系统上工作:
据我了解,这样做是非常糟糕的:
struct X_chaotic
{
bool flag1;
double d1;
bool flag2;
double d2;
bool flag3;
double d3;
//... and so on ...
};
根据C++ Alignment, Cache Line and Best Practice
和 Data structure alignment,
应该是 better/faster 并且这样写更紧凑:
struct X_alignOrder
{
double d1;
double d2;
double d3;
//... all other doubles ...
bool flag1;
bool flag2;
bool flag3;
//... all other bools ...
};
成员按照对齐大小的顺序声明,从最高对齐开始。
可以肯定地说按对齐大小对数据成员的声明排序是个好主意吗?你会说这是最佳实践吗?还是没有区别?
(我听说由于 C++ 标准,编译器无法重新排列定义的顺序,这甚至适用于在 class 的访问说明符块中声明的所有数据成员)
因为我从未在 Scott Meyers 的书中或 Bjarne Stroustrup 的书中读到过这方面的内容,所以我想知道我是否应该开始通过日常工作的对齐方式重新排序数据声明。
这比看起来要复杂。
通过根据对齐需要对成员进行排序,您将节省一些填充字节,并且总大小会更小。 可能如果内存紧张或者这意味着该类型可以放入单个缓存行而不是两个或三个缓存行中,这对您很重要。
另一方面;如果您经常访问过去靠得很近的成员,那么以前它们通常会被 CPU 预取器一起拉入缓存,但现在重组 class 后不会了。那么您可能会节省内存,但会牺牲 运行时间性能。
这里的性能也可能因不同的 CPU 和不同的 compilers/compiler 选项而有很大差异。
您需要 运行 在您的实际环境中进行一些基准测试,以了解哪种性能最适合您。
另请记住,重新排列成员变量会改变初始化顺序,如果成员相互依赖,这可能很重要(foo 初始化 bar,因此 foo 需要先初始化,等等)。
是的。从理论上讲,如果您关心性能,则数据结构的对齐很重要。这也是很好的编程习惯。
大多数时候,数据结构的对齐方式是根据 'struct' 中最宽的成员设置的。通常,您的编译器会为您处理。但是,在插入前导填充时,C++ 和 C 的行为可能不同。
您可以使用 offsetof
macro 来评估 size_t
中给定 struct
成员的距离。不过,这是 ANSI C。
#include <stdio.h>
#include <stddef.h>
typedef struct Test_t {
char *p;
char c;
int i;
long l;
} Test;
int main(){
printf("offsetof(Test,p) = %zu\n", offsetof(Test,p));
printf("offsetof(Test,c) = %zu\n", offsetof(Test,c));
printf("offsetof(Test,i) = %zu\n", offsetof(Test,i));
printf("offsetof(Test,l) = %zu\n", offsetof(Test,l));
return 0;
}
这将打印
offsetof(Test,p) = 0
offsetof(Test,c) = 8
offsetof(Test,i) = 12
offsetof(Test,l) = 16
考虑在具有以下类型对齐的 x64 位 Windows 操作系统上工作:
据我了解,这样做是非常糟糕的:
struct X_chaotic
{
bool flag1;
double d1;
bool flag2;
double d2;
bool flag3;
double d3;
//... and so on ...
};
根据C++ Alignment, Cache Line and Best Practice 和 Data structure alignment, 应该是 better/faster 并且这样写更紧凑:
struct X_alignOrder
{
double d1;
double d2;
double d3;
//... all other doubles ...
bool flag1;
bool flag2;
bool flag3;
//... all other bools ...
};
成员按照对齐大小的顺序声明,从最高对齐开始。
可以肯定地说按对齐大小对数据成员的声明排序是个好主意吗?你会说这是最佳实践吗?还是没有区别?
(我听说由于 C++ 标准,编译器无法重新排列定义的顺序,这甚至适用于在 class 的访问说明符块中声明的所有数据成员)
因为我从未在 Scott Meyers 的书中或 Bjarne Stroustrup 的书中读到过这方面的内容,所以我想知道我是否应该开始通过日常工作的对齐方式重新排序数据声明。
这比看起来要复杂。
通过根据对齐需要对成员进行排序,您将节省一些填充字节,并且总大小会更小。 可能如果内存紧张或者这意味着该类型可以放入单个缓存行而不是两个或三个缓存行中,这对您很重要。
另一方面;如果您经常访问过去靠得很近的成员,那么以前它们通常会被 CPU 预取器一起拉入缓存,但现在重组 class 后不会了。那么您可能会节省内存,但会牺牲 运行时间性能。
这里的性能也可能因不同的 CPU 和不同的 compilers/compiler 选项而有很大差异。
您需要 运行 在您的实际环境中进行一些基准测试,以了解哪种性能最适合您。
另请记住,重新排列成员变量会改变初始化顺序,如果成员相互依赖,这可能很重要(foo 初始化 bar,因此 foo 需要先初始化,等等)。
是的。从理论上讲,如果您关心性能,则数据结构的对齐很重要。这也是很好的编程习惯。
大多数时候,数据结构的对齐方式是根据 'struct' 中最宽的成员设置的。通常,您的编译器会为您处理。但是,在插入前导填充时,C++ 和 C 的行为可能不同。
您可以使用 offsetof
macro 来评估 size_t
中给定 struct
成员的距离。不过,这是 ANSI C。
#include <stdio.h>
#include <stddef.h>
typedef struct Test_t {
char *p;
char c;
int i;
long l;
} Test;
int main(){
printf("offsetof(Test,p) = %zu\n", offsetof(Test,p));
printf("offsetof(Test,c) = %zu\n", offsetof(Test,c));
printf("offsetof(Test,i) = %zu\n", offsetof(Test,i));
printf("offsetof(Test,l) = %zu\n", offsetof(Test,l));
return 0;
}
这将打印
offsetof(Test,p) = 0
offsetof(Test,c) = 8
offsetof(Test,i) = 12
offsetof(Test,l) = 16