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

Posted

技术标签:

【中文标题】C++ 在堆上分配相同类型的变量会花费截然不同的时间【英文标题】:C++ Allocating same type of variables on the heap costs tremendously different amount of time 【发布时间】:2015-03-22 02:01:28 【问题描述】:

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

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;

这需要将近 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;
 

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

*a = y;

double temp=y;
*a = temp;

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

【问题讨论】:

y 正在改变,x 没有改变,编译器足够聪明,可以解决这个问题并优化重复分配(可能还有所有循环)。 【参考方案1】:
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;

在您的 OUTER LOOP 中,您反复将 *a 指定为 xy

在您的 INNER LOOP 中,您要么将 x 重复设置为 1000,要么将 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;

然后

for (int i = 0; i < 1000000; i++) 
  double y = 0;
  // INNER LOOP:
  for (int j = 0; j < 1000; j++)
    y++;
  
  *a = 1000; //*a = y;

然后

for (int i = 0; i < 1000000; i++) 
  double y = 0;
  // INNER LOOP:
  for (int j = 0; j < 1000; j++)
    y++;
  
  //*a = y;

*a = 1000; 

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

for (int i = 0; i < 1000000; i++) 
  // INNER LOOP:
  for (int j = 0; j < 1000; j++)
  

*a = 1000; 

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

*a = 1000; 

另一方面,在y 上执行y++ 1000通常与执行y += 1000 相同,因为y 开始时可能足够大,以至于浮点舍入会导致问题。在这种情况下它不是真的,因为在将 +1 加到 0. 1000 次时不会发生舍入,但在 general 中它不是真的。因为证明不存在四舍五入是困难的——而且让它完全正确更困难——编译器编写者可能没有处理这种情况。

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

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

【讨论】:

【参考方案2】:

如果您分配*a = y,程序需要结束循环才能知道分配给a的值。

在另一种情况下,因为你没有修改 x 的值,而是总是分配一些 constexpr,所以可以将它从循环中取出,而循环实际上从未真正执行过,因为它没有任何对外界的影响。

【讨论】:

以上是关于C++ 在堆上分配相同类型的变量会花费截然不同的时间的主要内容,如果未能解决你的问题,请参考以下文章

我应该啥时候在堆上分配? (C++)

在堆上创建的变量,指向同一个变量的2个指针有不同的地址?

在堆上“分解” c++ 数组是不是安全?

C++ 类可以确定它是在堆栈上还是在堆上?

go的值类型和引用类型2——内存分配规则

C++:在堆上分配一个大的可变大小的二维向量