Итак, я собираюсь обосновать выбор № 3.Рассмотрим следующий код:
struct Foo {
int x;
int y;
};
Foo
add(Foo a, Foo b)
{
Foo result;
result.x = a.x + b.x;
result.y = a.y + b.y;
return result;
}
Foo
add2(Foo &a, Foo &b)
{
Foo result;
result.x = a.x + b.x;
result.y = a.y + b.y;
return result;
}
Попробуйте проверить созданную сборку.Обратите внимание на то, как add
почти целиком регистрирует операции, хорошо спланированные.Обратите внимание, что add2
- это множество обращений к памяти без переупорядочения.
Я написал main
, который вызывал каждую из этих функций 10 миллиардов раз.Результат?add
заняло 22 секунды, в то время как add2
заняло 26 секунд.Даже для этого тривиального примера это на 10-20% лучше производительности для версии с передачей по значению.
ОК, поэтому структура тривиальна.Но такова и функция.Чем сложнее функция, тем больше вероятность, что версия с передачей по значению будет быстрее, потому что компилятор знает, что два аргумента не «перекрываются».Это огромное преимущество для оптимизации.
Конечно, это решение должно в первую очередь основываться на семантике функции: вам нужно, чтобы значение NULL было допустимым?Если это так, очевидно, вам нужен указатель.Вам нужно изменить объекты?Затем используйте указатель или ссылку.
Но если вам не нужно изменять объекты, предпочитайте передавать их по значению, если объекты не большие и / или не имеют нетривиального конструктора копирования (например, std:: строка).Если значение на самом деле слишком медленное, передайте по ссылке-на-константе или на указатель на константу.
Но не стоит недооценивать потенциальные преимущества скорости передачи по значению, которые вытекают из преимуществ регистров иПереупорядочение памяти и команд.И обратите внимание, что эти преимущества становятся более заметными с каждым поколением процессоров.