在书中Game Coding Complete, 3rd Edition,作者提到了一种既可以减小数据结构大小又可以 提高访问性能的技术。从本质上讲,它依赖于这样一个事实:当成员变量与内存对齐时,您可以获得性能。这是编译器可以利用的明显潜在优化,但通过确保每个变量对齐,它们最终会膨胀数据结构的大小。
或者至少这是他的主张。
他说,真正的性能提升是通过动脑并确保您的结构设计得当,以利用速度提升的优势,同时防止编译器膨胀。他提供了以下代码片段:
#pragma pack( push, 1 )
struct SlowStruct
{
char c;
__int64 a;
int b;
char d;
};
struct FastStruct
{
__int64 a;
int b;
char c;
char d;
char unused[ 2 ]; // fill to 8-byte boundary for array use
};
#pragma pack( pop )
在未指定的测试中使用上述 struct
对象,他报告了 15.6%
的性能提升(222ms
与 192ms
) 和更小的 FastStruct
大小。这一切对我来说在纸面上都有意义,但在我的测试下却站不住脚:
同时产生和大小(计算char unused[ 2 ]
)!
现在,如果 #pragma pack( push, 1 )
仅与 FastStruct
隔离(或完全删除),我们确实会看到不同之处:
所以,最后,问题来了:现代编译器(特别是 VS2010)是否已经针对位对齐进行了优化,因此没有提高性能(但增加结构大小作为副作用,如 Mike Mcshaffry 所说)?还是我的测试强度不够/不确定,无法返回任何重要结果?
对于测试,我在未对齐的 __int64
成员上执行了各种任务,包括数学运算、列主多维数组遍历/检查、矩阵运算等。这两种结构都不会产生不同的结果。
最后,即使它们没有性能提升,这仍然是一个有用的花絮,需要牢记以将内存使用量保持在最低限度。但是,如果我只是没有看到性能提升(无论多么小),我会很高兴。
最佳答案
高度依赖于硬件。
让我演示一下:
#pragma pack( push, 1 )
struct SlowStruct
{
char c;
__int64 a;
int b;
char d;
};
struct FastStruct
{
__int64 a;
int b;
char c;
char d;
char unused[ 2 ]; // fill to 8-byte boundary for array use
};
#pragma pack( pop )
int main (void){
int x = 1000;
int iterations = 10000000;
SlowStruct *slow = new SlowStruct[x];
FastStruct *fast = new FastStruct[x];
// Warm the cache.
memset(slow,0,x * sizeof(SlowStruct));
clock_t time0 = clock();
for (int c = 0; c < iterations; c++){
for (int i = 0; i < x; i++){
slow[i].a += c;
}
}
clock_t time1 = clock();
cout << "slow = " << (double)(time1 - time0) / CLOCKS_PER_SEC << endl;
// Warm the cache.
memset(fast,0,x * sizeof(FastStruct));
time1 = clock();
for (int c = 0; c < iterations; c++){
for (int i = 0; i < x; i++){
fast[i].a += c;
}
}
clock_t time2 = clock();
cout << "fast = " << (double)(time2 - time1) / CLOCKS_PER_SEC << endl;
// Print to avoid Dead Code Elimination
__int64 sum = 0;
for (int c = 0; c < x; c++){
sum += slow[c].a;
sum += fast[c].a;
}
cout << "sum = " << sum << endl;
return 0;
}
酷睿 i7 920 @ 3.5 GHz
slow = 4.578
fast = 4.434
sum = 99999990000000000
好吧,区别不大。但它在多次运行中仍然保持一致。
因此对齐方式在 Nehalem Core i7 上产生了很小的差异。
Intel Xeon X5482 Harpertown @ 3.2 GHz(Core 2 - generation Xeon)
slow = 22.803
fast = 3.669
sum = 99999990000000000
现在看看...
快 6.2 倍!!!
结论:
您会看到结果。您决定是否值得花时间进行这些优化。
编辑:
相同的基准测试,但没有 #pragma pack
:
酷睿 i7 920 @ 3.5 GHz
slow = 4.49
fast = 4.442
sum = 99999990000000000
Intel Xeon X5482 Harpertown @ 3.2 GHz
slow = 3.684
fast = 3.717
sum = 99999990000000000
- Core i7 的编号没有改变。显然它可以处理 对于这个基准没有问题的错位。
- Core 2 Xeon 现在显示两个版本的时间相同。这证实了未对齐是 Core 2 架构上的一个问题。
摘 self 的评论:
如果您省略 #pragma pack
,编译器将保持所有内容对齐,因此您不会看到此问题。所以这实际上是一个示例,说明如果您误用 #pragma pack
可能会发生什么。
关于c++ - 用于空间和性能提升的位对齐,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9086656/