C++在堆上分配相同类型的变量会花费极大的不同时间

0 c++ performance pointers memory-management visual-c++

我在运行大量数据时遇到性能问题.为简单起见,我记下以下代码:

double *a = new double();
for (int i = 0; i < 1000000; i++){ 
    double x = 0;
    double y = 0;
    for (int j = 0; j < 1000; j++){
        x = 1000;
        y++;
    }
    *a = x; //*a = y;
}
Run Code Online (Sandbox Code Playgroud)

这需要将近0毫秒.但是,如果我将y分配给*a:

double *a = new double();
for (int i = 0; i < 1000000; i++){ 
    double x = 0;
    double y = 0;
    for (int j = 0; j < 1000; j++){
        x = 1000;
        y++;
    }
    *a = y; //*a = x;
} 
Run Code Online (Sandbox Code Playgroud)

这需要763毫秒,这比第一种情况要长得多.我发现这是由循环中相对更复杂的y计算引起的.但我不知道为什么会这样.如果我改变

*a = y;
Run Code Online (Sandbox Code Playgroud)

double temp=y;
*a = temp;
Run Code Online (Sandbox Code Playgroud)

这仍然花费近763毫秒.无论我如何转移价值,似乎我都无法有效地将y的值分配给*a.任何人都可以解释为什么在完成内循环后y与x有显着差异?为什么即使我将y的值转移到其他临时变量,仍然需要很长时间才能将该值赋给*a?(顺便说一句,如果'a'是double而不是指向double的指针,则指定y和x的值之间没有区别.)

Yak*_*ont 6

double *a = new double();
// OUTER LOOP:
for (int i = 0; i < 1000000; i++){ 
  double x = 0;
  double y = 0;
  // INNER LOOP:
  for (int j = 0; j < 1000; j++){
    x = 1000;
    y++;
  }
  *a = x; //*a = y;
}
Run Code Online (Sandbox Code Playgroud)

在你的外环中,你反复指定*axy.

在你的内循环,你要么设置x1000反复,或者你增加y 1000倍.

现在,编译器知道x=1000后面的x=1000内容相当于只执行一次.因此,优化代码非常容易,如下所示:

double *a = new double();
// OUTER LOOP:
for (int i = 0; i < 1000000; i++){ 
  constexpr double x = 1000;
  double y = 0;
  // INNER LOOP:
  for (int j = 0; j < 1000; j++){
    y++;
  }
  *a = x; //*a = y;
}
Run Code Online (Sandbox Code Playgroud)

然后

for (int i = 0; i < 1000000; i++){ 
  double y = 0;
  // INNER LOOP:
  for (int j = 0; j < 1000; j++){
    y++;
  }
  *a = 1000; //*a = y;
}
Run Code Online (Sandbox Code Playgroud)

然后

for (int i = 0; i < 1000000; i++){ 
  double y = 0;
  // INNER LOOP:
  for (int j = 0; j < 1000; j++){
    y++;
  }
  //*a = y;
}
*a = 1000; 
Run Code Online (Sandbox Code Playgroud)

因为这些操作都是合法的.完成后,您所做的所有工作y都没有副作用(因为在这种情况下我们从未将其分配*a),因此变量y被消除:

for (int i = 0; i < 1000000; i++){ 
  // INNER LOOP:
  for (int j = 0; j < 1000; j++){
  }
}
*a = 1000; 
Run Code Online (Sandbox Code Playgroud)

这使得这些循环变空.并且可以消除空循环(编译器甚至不必证明它们终止!),留下这个:

*a = 1000; 
Run Code Online (Sandbox Code Playgroud)

在另一方面,这样做y++ 1000的时候y不是通常一样做y += 1000,由于该可能性y开始了足够大的浮点舍入导致的问题.在这种情况下,它是不正确的,如添加+1到时将不会发生舍入0.1000倍,但它是不是真的在一般.因为证明不存在舍入是很难的 - 并且完全正确地更难实现 - 编译器编写者可能没有处理这种情况.

这留下了一个更复杂的代码来进行优化,编译器很难确定循环的每次迭代都是完全相同的,因此您已经观察到优化器失败了.

在这种情况下它可以优化多少,我们必须检查反汇编.