C++ 在堆上分配相同类型的变量会花费非常不同的时间
C++ Allocating same type of variables on the heap costs tremendously different amount of time
我在处理 运行 大数据集时遇到了性能问题。为了简单起见,我记下了以下代码:
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 的值没有区别。)
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 中则不正确。因为证明没有舍入是困难的——而且完全正确更难——编译器作者可能没有处理这种情况。
这留下了更复杂的代码来优化,编译器很难确定循环的每次迭代是否完全相同,因此您观察到优化器失败了。
在这种情况下它可以优化多少我们必须检查反汇编。
在您分配 *a = y
的情况下,程序需要结束循环才能知道要为 a.
分配什么值
另一种情况,因为你没有修改x的值,而是一直赋值一些constexpr,所以可以从循环中取出来,实际上这个循环从来没有真正执行过,因为它不对外界有什么影响。
我在处理 运行 大数据集时遇到了性能问题。为了简单起见,我记下了以下代码:
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 的值没有区别。)
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 中则不正确。因为证明没有舍入是困难的——而且完全正确更难——编译器作者可能没有处理这种情况。
这留下了更复杂的代码来优化,编译器很难确定循环的每次迭代是否完全相同,因此您观察到优化器失败了。
在这种情况下它可以优化多少我们必须检查反汇编。
在您分配 *a = y
的情况下,程序需要结束循环才能知道要为 a.
另一种情况,因为你没有修改x的值,而是一直赋值一些constexpr,所以可以从循环中取出来,实际上这个循环从来没有真正执行过,因为它不对外界有什么影响。