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
指定为 x
或 y
。
在您的 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++ 在堆上分配相同类型的变量会花费截然不同的时间的主要内容,如果未能解决你的问题,请参考以下文章