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

StackOverflow https://stackoverflow.com/questions/722257

  •  05-09-2019
  •  | 
  •  

Вопрос

Является ли один из них более быстрым?

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

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

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

Это было полезно?

Решение

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

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

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

Другие советы

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

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

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

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

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

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

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

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

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

Бонус:Для более быстрого использования restrict начиная с c99 (таким образом, вы переходите на fortran, который ограничивает псевдонимирование указателя;пример использования: 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

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

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

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

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

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

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

Аргумент в пользу скорости...обычно.

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

В целом,

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

Объявляйте входной примитив как ссылку или 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& )"
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top