繁体   English   中英

堆栈分配功能(性能)

[英]Stack allocation feature (performance)

在我的小型性能问题调查期间,我注意到一个有趣的堆栈分配功能,这里是测量时间的模板:

#include <chrono>
#include <iostream>

using namespace std;
using namespace std::chrono;

int x; //for simple optimization suppression
void foo();

int main()
{   
    const size_t n = 10000000; //ten millions
    auto start = high_resolution_clock::now();

    for (size_t i = 0; i < n; i++)
    {
        foo();
    }

    auto finish = high_resolution_clock::now();
    cout << duration_cast<milliseconds>(finish - start).count() << endl;
}

现在全部关于foo()实现,在每个实现中将分配总计500000 ints

  1. 分配在一个块中:

     void foo() { const int size = 500000; int a1[size]; x = a1[size - 1]; } 

    结果: 7.3秒 ;

  2. 分为两个部分:

     void foo() { const int size = 250000; int a1[size]; int a2[size]; x = a1[size - 1] + a2[size - 1]; } 

    结果: 3.5秒 ;

  3. 分为四个部分:

     void foo() { const int size = 125000; int a1[size]; int a2[size]; int a3[size]; int a4[size]; x = a1[size - 1] + a2[size - 1] + a3[size - 1] + a4[size - 1]; } 

    结果: 1.8秒

等等......我将它分成16个块 ,结果时间为0.38秒


请向我解释一下,为什么以及如何发生这种情况?
我使用了MSVC 2013(v120),发布版本。

UPD:
我的机器是x64平台。 我用Win32平台编译它。
当我用x64平台编译它时,它在所有情况下产生大约40ms。
为什么平台选择如此影响?

从VS2015 Update 3的反汇编看,在foo的2和4数组版本中,编译器优化了未使用的数组,以便它只为每个函数中的1个数组保留堆栈空间。 由于后面的函数具有较小的数组,因此花费的时间更少 对x的赋值为两个/所有4个数组读取相同的内存位置。 (由于数组未初始化,因此从它们读取是未定义的行为。)如果不优化代码,则会有2或4个不同的数组从中读取。

这些函数花费的时间很长,这是由于__chkstk执行的堆栈探测作为堆栈溢出检测的一部分(当编译器需要超过1页的空间来保存所有局部变量时,这是必需的)。

您应该查看生成的汇编程序代码,以查看编译器对代码的真正作用。 对于gcc / clang / icc,您可以使用Matt Godbolt的Compiler Explorer

由于UB, clang优化了一切,结果是( foo - 第一版, foo2 - 第二版:

foo:                                    # @foo
        retq

foo2:                                   # @foo2
        retq

icc对待这两个版本非常相似:

foo:
        pushq     %rbp                                          #4.1
        movq      %rsp, %rbp                                    #4.1
        subq      $2000000, %rsp                                #4.1
        movl      -4(%rbp), %eax                                #8.9
        movl      %eax, x(%rip)                                 #8.5
        leave                                                   #10.1
        ret                                                     #10.1

foo2:
        pushq     %rbp                                          #13.1
        movq      %rsp, %rbp                                    #13.1
        subq      $2000000, %rsp                                #13.1
        movl      -1000004(%rbp), %eax                          #18.9
        addl      -4(%rbp), %eax                                #18.24
        movl      %eax, x(%rip)                                 #18.5
        leave                                                   #19.1
        ret 

gcc为不同的版本创建不同的汇编代码。 版本6.1生成的代码将显示与您的实验类似的行为:

foo:
        pushq   %rbp
        movq    %rsp, %rbp
        subq    $2000016, %rsp
        movl    1999996(%rsp), %eax
        movl    %eax, x(%rip)
        leave
        ret
foo2:
        pushq   %rbp
        movl    $1000016, %edx  #only the first array is allocated
        movq    %rsp, %rbp
        subq    %rdx, %rsp
        leaq    3(%rsp), %rax
        subq    %rdx, %rsp
        shrq    $2, %rax
        movl    999996(,%rax,4), %eax
        addl    999996(%rsp), %eax
        movl    %eax, x(%rip)
        leave
        ret

因此,理解上的差异的唯一方法是看你的编译器产生的汇编代码,其他一切都只是猜测。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM