【问题标题】:Should we use temporary variables for the returned values of functions?我们应该为函数的返回值使用临时变量吗?
【发布时间】:2012-12-19 16:53:16
【问题描述】:

我想了想:这两种做法是否存在性能差异:

  1. 将函数的返回值存储在临时变量中 将该变量作为参数提供给另一个函数。
  2. 将该函数放入另一个函数中。

规格

假设所有类和函数都正确编写。

案例 1。

ClassA a = function1();
ClassB b = function2(a);
function3(b);

案例 2。

function3(function2(function1()));

我知道只运行一次没有太大区别,但假设我们可以在一个循环中运行很多次,我创建了一些测试。

测试

#include <iostream>
#include <ctime>
#include <math.h>
using namespace std;

int main()
{
   clock_t start = clock();
   clock_t ends = clock();

   // Case 1.
   start = clock();
   for (int i=0; i<10000000; i++)
   {
      double a = cos(1);
      double b = pow(a, 2);
      sqrt(b);
   }
   ends = clock();
   cout << (double) (ends - start) / CLOCKS_PER_SEC << endl;

   // Case 2.
   start = clock();
   for (int i=0; i<10000000; i++)
      sqrt(pow(cos(1),2));
   ends = clock();
   cout << (double) (ends - start) / CLOCKS_PER_SEC << endl;
   return 0;
}

结果

  • 案例 1 = 6.375
  • 案例 2 = 0.031

为什么第一个慢得多,如果第二个更快,为什么我们不总是那样写代码?反正第二个练习有名字吗?
我还想知道如果我在第一种情况下在 for 循环之外创建变量会发生什么,但结果是一样的。为什么?

【问题讨论】:

  • 第二个比较慢?对我来说似乎要快得多。
  • 优化编译器应该能够使这两种情况相同.. 除非您首先调用 UB。
  • @Linuxios:永远不会得到结果。正常的优化编译器完全可以删除像这样固有的函数调用,因此它不知道副作用,但不会采用该值。
  • @Linuxios sqrt, sincos 是内置的,可能编译器知道它们没有任何副作用,所以sqrt(pow(cos(1),2)); 相当于(例如)@ 987654328@ - 一个没有效果的表达式,只是一个悬在空中的值......
  • 对两个循环的正确全面优化是“不做循环”。你需要一个更好的测试。

标签: c++ performance


【解决方案1】:

如果您希望计算紧缩并且您的数字变得更加一致,请打破一次性优化。确保获得正确值的代码实际运行而不是完全丢弃,我已将 both 测试中的结果分配给 volatile 本地(这不是 volatile 的正确用法,但确实仅确保价值创造的体面工作是显着的增量)。

#include <iostream>
#include <ctime>
#include <cmath>
using namespace std;

int main()
{
    clock_t start;
    volatile double val;

    for (int j=1;j<=10;j++)
    {
        // Case 1.
        start = clock();
        for (int i=0; i<2000000; i++)
        {
            double a = cos(1);
            double b = pow(a, 2);
            val = sqrt(b);
        }
        cout << j << ':' << (double) (clock() - start) / CLOCKS_PER_SEC << endl;

        // Case 2.
        start = clock();
        for (int i=0; i<2000000; i++)
            val = sqrt(pow(cos(1),2));
        cout << j << ':' << (double) (clock() - start) / CLOCKS_PER_SEC << endl << endl;
    }
    return 0;
}

在我的 Macbook Air 上生成以下发布编译的输出(无论如何都不是速度恶魔):

1:0.001465
1:0.001305

2:0.001292
2:0.001424

3:0.001297
3:0.001351

4:0.001366
4:0.001342

5:0.001196
5:0.001376

6:0.001341
6:0.001303

7:0.001396
7:0.001422

8:0.001429
8:0.001427

9:0.001408
9:0.001398

10:0.001317
10:0.001353

【讨论】:

  • 我明白你在说什么,但这不是因为编译器优化,我们应该像案例 2 那样编写代码吗?
  • @totymedli 并非如此,人们很容易争辩说,第一个的清晰度比第二个更能吸引开发人员的眼球,因为优化器无论如何都会抛出ab(在此case) 最终结果是一样的。对于非平凡的价值类型,每种方法/是否是好的做法或坏的做法是一个问题,人们对 RVO(返回值优化)有更好的理解,而我可能无法更好地回答。对于这种特殊情况,可能会生成相同或几乎相同的代码。
【解决方案2】:

对上述两个循环进行适当且合法的全面优化是“甚至不执行循环”。您可能很容易看到这样一种情况:在第一种情况下使用未初始化的变量会混淆编译器,或者您使用变量可能会混淆它,或者您的优化级别可能会强制命名变量实际存在。

现在,在 C++11 中两者之间存在临时变量隐式移动的差异,但您可以使用 std::move 解决此问题。 (我不确定,但最后一次使用超出范围的局部变量可能有资格进行隐式移动)。对于double,这没有区别,但对于更复杂的类型,则可以。

【讨论】:

  • 最后一次使用超出范围的变量不符合隐式移动的条件。隐式移动仅发生在可能发生复制省略的地方——returns 局部变量(其中变量的类型与返回值的类型匹配),throws 在 @ 之前超出范围的变量987654325@,临时副本和catchs 异常(异常类型完全匹配)。
猜你喜欢
  • 2015-01-08
  • 1970-01-01
  • 2012-11-01
  • 2012-09-03
  • 2019-09-03
  • 2021-10-03
  • 1970-01-01
  • 2020-07-16
  • 1970-01-01
相关资源
最近更新 更多