Должны ли мы использовать временные переменные для возвращаемых значений функций?

Я подумал об этом: есть ли разница в производительности в этих двух практиках:

  • Сохранять возвращаемое значение функции во временной переменной, чем укажите эту переменную как параметр для другой функции.
  • Поместите функцию в другую функцию.

Спецификация

Предположим, что все классы и функции написаны правильно.

Случай 1.

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

Случай 2.

function3(function2(function1()));

Я знаю, что нет большой разницы только с одним прогоном, но предположил, что мы могли бы запускать это много раз в цикле, я создал несколько тестов.

Test

#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 в первом случае, но результат был таким же. Почему?

Ответ 1

Разбейте оптимизацию throw-this-all-away, если вы хотите, чтобы вычислительный хруст и ваши номера стали намного более последовательными. Обеспечение того, чтобы код, чтобы получить правильное значение, фактически выполнялся, а не полностью выкидывался, я назначил результаты в обоих тестах на летучие локальные (что не совсем правильное использование изменчивости, но делает достойную работу по обеспечению только значение-создание является значительной дельта).

#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

Правильная и законная полная оптимизация обоих циклов выше - "даже не делать цикл". Вы можете легко увидеть случай, когда вы путаете компилятор с помощью неинициализированной переменной в первом случае, или, может быть, ваше использование переменных смущает ее, или, может быть, ваш уровень оптимизации заставляет названные переменные фактически существовать.

Теперь существует разница между двумя в С++ 11, включающая неявные перемещения временных переменных, но вы можете исправить это с помощью std::move. (Я не уверен, но последнее использование локальной переменной, выходящей из области видимости, может претендовать на неявный ход). При a double это не разница, но для более сложных типов это может быть.