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

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

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

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

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

Ответы [ 9 ]

25 голосов
/ 06 апреля 2009

Это не имеет значения. В обоих случаях код будет встроен в то же самое. Излишне копирование int (в передаче по значению) будет исключено компилятором, а создание ненужной ссылки на int и последующий уровень косвенности при доступе к int также будет исключено.

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

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

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

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

19 голосов
/ 06 апреля 2009

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

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

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

7 голосов
/ 06 апреля 2009

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

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

2 голосов
/ 06 марта 2015

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

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

Бонус: для более быстрой скорости используйте restrict от c99 (таким образом, вы догоняете фортран, который ограничивает наложение указателей; прецедент: f(const T&__restrict__ obj). Стандарт C ++ не допускает ключевое слово 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
0 голосов
/ 28 сентября 2011

В общем

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

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

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& )"
0 голосов
/ 07 апреля 2009

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

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

Аргумент за скорость ... обычно.

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

0 голосов
/ 06 апреля 2009

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

0 голосов
/ 06 апреля 2009

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

0 голосов
/ 06 апреля 2009

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

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

...