在我的小型性能问题调查期间,我注意到了一个有趣的堆栈分配功能,这里是测量时间的模板:
#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
:
分配在一个块中:
void foo()
{
const int size = 500000;
int a1[size];
x = a1[size - 1];
}
结果: 7.3秒;
分配在两个块中:
void foo()
{
const int size = 250000;
int a1[size];
int a2[size];
x = a1[size - 1] + a2[size - 1];
}
结果: 3.5秒;
分配在四个块中:
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
为什么平台选择如此影响?
答案 0 :(得分:8)
在VS2015 Update 3的反汇编中,在foo
的2和4数组版本中,编译器优化了未使用的数组,以便它只为每个函数中的1个数组保留堆栈空间。由于后面的函数具有较小的数组,因此花费的时间更少对x的赋值为两个/所有4个数组读取相同的内存位置。 (由于数组是未初始化的,因此从它们读取是未定义的行为。)如果不优化代码,则可以读取2个或4个不同的数组。
这些函数花费很长时间是由于__chkstk执行的堆栈探测作为堆栈溢出检测的一部分(当编译器需要超过1页的空间来容纳所有局部变量时,这是必需的)。
答案 1 :(得分:1)
您应该查看生成的汇编程序代码,以查看编译器对代码的真正作用。对于gcc / clang / icc,您可以使用Matt Godbolt's Compiler Explorer。
由于UB, clang 优化了所有内容,结果是(@Component({
selector: 'clicker-form',
templateUrl: 'build/components/clickerForm/clickerForm.html',
directives: [Button, Icon, Item, Label, TextInput, FORM_DIRECTIVES, REACTIVE_FORM_DIRECTIVES],
})
- 第一版,foo
- 第二版:
foo2
icc 将两个版本视为非常相似:
foo: # @foo
retq
foo2: # @foo2
retq
和 gcc 为不同版本创建不同的汇编程序代码。版本6.1生成的代码显示与您的实验类似的行为:
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
因此理解差异的唯一方法是查看你的编译器生成的汇编程序代码,其他一切只是猜测。