请查看以下代码示例,使用Visual Studio 2010在Windows-32系统上执行:
#include <iostream>
using namespace std;
class LogicallyClustered
{
bool _fA;
int _nA;
char _cA;
bool _fB;
int _nB;
char _cB;
};
class TypeClustered
{
bool _fA;
bool _fB;
char _cA;
char _cB;
int _nA;
int _nB;
};
int main(int argc, char* argv[])
{
cout << sizeof(LogicallyClustered) << endl; // 20
cout << sizeof(TypeClustered) << endl; // 12
return 0;
}
问题1
sizeof
这两个类的不同之处在于编译器正在插入填充字节以实现变量的优化内存对齐 . 它是否正确?
问题2
如果按照 class TypeClustered
中的类型对变量进行聚类,为什么内存占用量会变小?
问题3
总是根据类型聚类成员变量是一个很好的经验法则吗?我是否应该根据它们的大小升序(bool,char,int,double ......)对它们进行排序?
EDIT
补充问题4
较小的内存占用将提高数据缓存效率,因为可以缓存更多对象,并避免完全内存访问“慢”RAM . 那么成员声明的排序和分组是否可以被视为(小)但易于实现的性能优化?
5 回答
1)绝对正确 .
2)它不小,因为它们是分组的,但是因为它们的方式是 ordered and grouped . 例如,如果一个接一个地声明4
chars
,它们可以打包成4个字节 . 如果您声明一个char
并且立即声明一个int
,则将插入3个填充字节,因为int
将需要与4个字节对齐 .3)不!您应该在一个类中对成员进行分组,以使该类变得更具可读性 .
重要说明:这是所有特定于平台/编译器的 . 不要把它带到ad-literam .
另一个注意事项 - 在某些平台上也存在一些小的性能提升,用于访问驻留在类实例的第一个
n
(变化)字节中的成员 . 因此,在课程开始时声明经常访问的成员可以导致小的速度增加 . 但是,这也不应该只是陈述一个事实,但绝不建议你这样做 .你是对的,大小不同,因为编译器在类LogicallyClustered中插入填充字节 . 编译器应该使用这样的内存布局:
您的
class TypeClustered
不需要任何填充字节,因为所有元素都是对齐的 . bool和char不需要对齐,int需要在4字节边界上对齐 .关于问题3,我会说(经常:-))“这取决于 . ”如果您处于一个内存占用无关紧要的环境中,我宁愿按逻辑排序以使代码更具可读性 . 如果您处于每个字节都很重要的环境中,您可以考虑移动成员以优化空间使用 .
除非没有极端的内存占用限制,否则将它们集群化 logically ,这样可以提高代码的可读性并易于维护 .
除非你确实有空间问题(即具有这种结构的非常非常大的矢量),不要担心它 . 否则:添加填充以进行对齐:在大多数机器上,例如,
double
将在8字节边界上对齐 . 根据类型重新组合所有成员,在开始时需要最大对齐的类型将导致最小的内存占用 .Q1:是的
Q2:取决于bool的大小(取决于AFAIK编译器) . 假设它是1个字节(如char),前4个成员一起使用4个字节,这与一个整数使用的一样多 . 因此,编译器不需要在整数前面插入对齐填充 .
问题3:如果您想按类型订购,尺寸下降是一个更好的主意 . 但是,这种聚类会妨碍可读性 . 如果要在所有情况下避免填充,只需确保每个需要大于1个字节的内存的变量从对齐边界开始 .
然而,对齐边界因架构而异 . 那是(除了可能不同的int大小)为什么相同的结构在不同的体系结构上可能具有不同的大小 . 通常可以安全地以sizeof(x)的倍数的偏移量启动每个成员x . 即,在
int d将从偏移量3开始,这不是sizeof(int)的倍数(在x86 / 64上= 4),所以你应该将它移到前面 . 但是,没有必要严格按类型进行聚类 .
一些编译器还提供了完全省略填充的可能性,例如,
__attribute((packed))__
in g . 但是,这可能会降低程序的速度,因为int实际上可能需要两次内存访问 .