位对齐空间和性能提升

sse*_*ell 9 c++ compiler-construction optimization performance visual-studio-2010

在" 游戏编码完成"第3版中,作者提到了一种减少数据结构大小提高访问性能的技术.从本质上讲,它依赖于当成员变量与内存对齐时获得性能的事实.这是编译器可以利用的明显潜在优化,但通过确保每个变量对齐,它们最终会膨胀数据结构的大小.

或者这至少是他的主张.

他说,真正的性能提升是通过使用你的大脑并确保你的结构设计得合理,以利用速度增加,同时防止编译器膨胀.他提供了以下代码片段:

#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 )
Run Code Online (Sandbox Code Playgroud)

struct在未指定的测试中使用上述对象,他报告了性能增加15.6%(222ms与之相比192ms)和更小的尺寸FastStruct.这对我来说都是有意义的,但它在我的测试中无法阻止:

在此输入图像描述

同时结果大小(计数char unused[ 2 ])!

现在,如果#pragma pack( push, 1 )仅隔离FastStruct(或完全删除),我们确实看到了差异:

在此输入图像描述

所以,最后,问题在于:现代编译器(特别是VS2010)已经针对比特对齐进行了优化,因此性能提升不足(但增加结构尺寸作为副作用,如Mike Mcshaffry所述)?或者我的测试不够密集/不确定以返回任何重要结果?

对于测试,我在未对齐__int64成员上执行了数学运算,列主要多维数组遍历/检查,矩阵运算等各种任务.这两种结构都没有产生不同的结果.

最后,即使它们没有性能提升,这仍然是一个有用的消息,要记住将内存使用量降至最低.但是,如果我没有看到性能提升(无论多么轻微),我会喜欢它.

Mys*_*ial 13

它高度依赖于硬件.

让我来证明:

#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;
}
Run Code Online (Sandbox Code Playgroud)

酷睿i7 920 @ 3.5 GHz

slow = 4.578
fast = 4.434
sum = 99999990000000000
Run Code Online (Sandbox Code Playgroud)

好的,差别不大.但它在多次运行中仍然保持一致.
因此,对齐在Nehalem Core i7上产生了很小的差异.


Intel Xeon X5482 Harpertown @ 3.2 GHz(酷睿2代Xeon)

slow = 22.803
fast = 3.669
sum = 99999990000000000
Run Code Online (Sandbox Code Playgroud)

现在来看看......

快6.2倍!


结论:

你看到了结果.您决定是否值得花时间进行这些优化.


编辑:

相同的基准但没有#pragma pack:

酷睿i7 920 @ 3.5 GHz

slow = 4.49
fast = 4.442
sum = 99999990000000000
Run Code Online (Sandbox Code Playgroud)

Intel Xeon X5482 Harpertown @ 3.2 GHz

slow = 3.684
fast = 3.717
sum = 99999990000000000
Run Code Online (Sandbox Code Playgroud)
  • Core i7号码没有变化.显然它可以毫无困难地处理错位.
  • Core 2 Xeon现在显示两个版本的相同时间.这证实了Core 2架构上的错位是一个问题.

取自我的评论:

如果省略#pragma pack,编译器会保持一致,所以你不会看到这个问题.所以这实际上是一个例子,说明如果你滥用 会发生什么#pragma pack.

  • 如果省略`#pragma pack`,编译器会保持一致,所以你*不要*看到这个问题.所以这实际上是一个例子,说明如果你滥用`#pragma pack`会发生什么. (6认同)

Pup*_*ppy 6

这种手部优化通常很长时间.如果您正在打包空间,或者如果您具有类似SSE类型的强制对齐类型,则对齐只是一个重要的考虑因素.编译器的默认对齐和打包规则是有意设计的,以显着提高性能,虽然手动调整它们可能是有益的,但通常不值得.

可能在您的测试程序中,编译器从不在堆栈中存储任何结构,只是将成员保存在寄存器中,这些寄存器没有对齐,这意味着它与结构大小或对齐方式完全无关.

事情就是:可能存在别名和其他与子字访问相关的恶意,并且访问整个单词比访问子字的速度慢.所以一般来说,如果你只是访问一个成员,那么在时间上打包比单词大小更有效率.

  • @ssell:这种优化只会变得越来越普遍.是的,总的来说它不值得. (3认同)