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

Один из них быстрее?

inline int ProcessByValue(int i)
{
    // process i somehow
}

inline int ProcessByReference(const int& i)
{
    // process i somehow
}

Я знаю, что интегральные типы должны передаваться по значению. Тем не менее, я обеспокоен тем, что компилятор может встроить ProcessByValue для хранения копии. Есть ли правило для этого?

Ответ 1

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

Если функция принимает примитивный тип, пропуск по значению имеет смысл. Некоторые люди, которых я знаю, будут жаловаться, если они будут переданы const ref (поскольку это "необязательно" ), но я не думаю, что буду жаловаться. Если функция принимает определенный пользователем тип и не изменяет этот параметр, то передать значение const ref имеет смысл.

Если это определенный пользователем тип и параметр изменен, то семантика функции будет определять, как она должна быть передана.

Ответ 2

Это не имеет значения. В обоих случаях код будет привязан к тому же. Неправильное копирование int (в pass-by-value) будет устранено компилятором и без необходимости будет создавать ссылку на int и после этого слоя косвенности при доступе к int также будет устранено.

Ваш вопрос, похоже, основан на некоторых ложных предположениях:

  • То, что ключевое слово inline действительно получит вашу встроенную функцию. (Возможно, но это не гарантировано)
  • Выбор ссылочного значения vs зависит от функции, являющейся встроенной. (Точные соображения производительности будут применяться к не-встроенной функции)
  • Что это имеет значение и что вы можете перехитрить компилятор с такими тривиальными изменениями, как это (компилятор будет применять одни и те же оптимизации в любом случае)
  • И что оптимизация фактически приведет к заметной разнице в производительности. (даже если это не так, разница будет настолько мала, чтобы быть пренебрежимо малой).

Я знаю, что интегральные типы должны быть передается по значению. Однако я что компилятор может inline ProcessByValue, чтобы содержать копия. Есть ли правило для этого?

Да, он создаст копию. Точно так же, как передача по ссылке создала бы ссылку. И тогда, по крайней мере, для простых типов, таких как ints, компилятор устранит их снова. Вложение функции не позволяет изменять поведение функции. Если вы создадите функцию для принятия аргумента значения, она будет вести себя так, как если бы ей был задан аргумент значения, независимо от того, была ли она встроена. Если вы определите функцию для получения ссылки, она будет вести себя так, как будто передала ссылку, независимо от того, была она или нет. Так что делайте то, что приводит к правильному поведению.

Ответ 3

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

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

Ответ 4

Переходите по значению, если тип меньше или сопоставим с указателем; например, int, char, double, small structs,...

Передать по ссылке для больших объектов; например, контейнеры STL. Я много читал о том, что компиляторы могут его оптимизировать, но они не выполнили мой простой эталон. Если вы не хотите тратить время на тестирование, используйте const T& obj.

Бонус: для более быстрой скорости используйте restrict из c99 (таким образом вы догоняете fortran, который ограничивает сглаживание указателя, используйте случай: f(const T&__restrict__ obj). Стандарт С++ не позволяет ключевое слово restrict, но компиляторы используют внутренние ключевые слова - g++ использует __restrict__. Если в коде нет сглаживания, коэффициент усиления отсутствует.

эталон с g++ 4.9.2:

Передача вектора по ссылке:

> cat inpoint.cpp
#include <vector>
#include <iostream>

using namespace std;

inline int show_size(const vector<int> &v) {
  return v.size();
}

int main(){
  vector<int> v(100000000);
  cout << show_size(v) << endl;
  return 0;
}
> g++ -std=c++14 -O2 inpoint.cpp; time ./a.out
100000000

real    0m0.330s
user    0m0.072s
sys     0m0.256s

Передача вектора по значению занимает в два раза больше времени:

> cat invalue.cpp
#include <vector>
#include <iostream>

using namespace std;

inline int show_size(vector<int> v) {
  return v.size();
}

int main(){
  vector<int> v(100000000);
  cout << show_size(v) << endl;
  return 0;
}
> g++ -std=c++14 -O2 invalue.cpp; time ./a.out
100000000

real    0m0.985s
user    0m0.204s
sys     0m0.776s

Ответ 5

Лучший способ понять это - создать тестовый стенд, который выполняет как сборку оптимизированных версий кода, так и проверку сборки. Вы сразу увидите, что происходит с вашим конкретным компилятором и вашим конкретным прецедентом.

Когда он действительно справится с этим, сделайте то, что, по вашему мнению, будет ожидать пользователь вашего класса от интерфейса. Когда у вас все это будет построено и работает, измерьте и узнайте, где ваши узкие места. Скорее всего, любая разница, которая может это сделать (и вряд ли что-либо сделать), будет заглушена большими проблемами производительности в другом месте вашего кода.

Ответ 6

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

Ответ 7

Очень короткий ответ: при принятии решения о том, следует ли передавать по ссылке или по значению обрабатывать встроенные и не встроенные функции одинаково.

Ответ 8

В случае примитивов это не имеет значения, потому что вы пропускаете только 4 байта.

Причиной передачи ссылки является то, что она имеет размер 4 байта и имеет резкий размер сокращения в случае пользовательских типов и больших строк.

Аргумент для скорости... обычно.

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

Ответ 9

В общем случае

Объявлять выходные примитивы только как ссылки.

Объявлять только примитив ввода как ссылку или const ref, если вам нужно запретить выражения:

int two = plus1( 1 );  //  compile error if plus1 is declared as "int plus1( int& )"

double y = sqrt( 1.1 * 2 );  // compile error if sqrt is declared as "double sqrt( const double& )"