Подтвердить что ты не робот

Что более эффективно: вернуть значение против Pass by reference?

В настоящее время я изучаю, как писать эффективный код на С++, и по поводу вызовов функций возникает вопрос. Сравнивая эту псевдокодную функцию:

not-void function-name () {
    do-something
    return value;
}
int main () {
    ...
    arg = function-name();
    ...
}

с этой иначе-идентичной функцией псевдокода:

void function-name (not-void& arg) {
    do-something
    arg = value;
}
int main () {
    ...
    function-name(arg);
    ...
}

Какая версия более эффективна и в каком отношении (время, память и т.д.)? Если это зависит от того, когда первый будет более эффективным, а когда более эффективным будет второй?

Изменить. Для контекста этот вопрос ограничен аппаратными независимыми от платформы различиями и, в основном, программным обеспечением. Существуют ли независимые от машины различия в производительности?

Изменить. Я не вижу, как это дубликат. Другой вопрос заключается в сравнении прохождения по ссылке (предыдущий код) при прохождении по значению (ниже):

not-void function-name (not-void arg)

Это не то же самое, что мой вопрос. Мое внимание не относится к тому, что является лучшим способом передать аргумент функции. На мой взгляд, это лучший способ передать результат out для переменной из внешней области.

4b9b3361

Ответ 1

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

  • Если вам нужно вернуть простой или базовый объект, производительность будет одинаковой в обоих случаях.

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

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

Ответ 2

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

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

Например, в некоторых проектах С++ скомпилирован для управляемого расширения Microsoft CLR (С++/CX). так как все, что уже есть ссылка на объект в куче, я думаю, что нет разницы.

Ответ не упрощен для неуправляемых компиляций. несколько вопросов приходят на ум, когда я думаю о "Будет ли XXX работать быстрее, чем YYY?", например:

  • Являетесь ли вы объектно-глупыми?
  • Поддерживает ли ваш компилятор оптимизацию возвращаемого значения?
  • Поддерживает ли ваш объект семантику Copy-only или копирует и перемещает?
  • Является ли объект упакован в соответствии (например, std::array) или имеет указатель на что-то в куче? (например, std::vector)?

Если я приведу конкретный пример, я предполагаю, что на MSVС++ и GCC возвращение std::vector по значению будет передаваться по ссылке, из-за r-value-оптимизации, и будет немного (на несколько наносекунд ) быстрее, чем возврат вектора путем перемещения. например, это может быть совершенно отличным от Clang.

В конечном итоге профилирование - единственный верный ответ.

Ответ 3

Возврат объекта должен использоваться в большинстве случаев из-за оптимизации copy elision.

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

Посмотрите, например, на std::getline, который берет std::string по ссылке. Эта функция предназначена для использования в качестве условия цикла и продолжает заполнять std::string до тех пор, пока не будет достигнута EOF. Использование того же std::string позволяет повторять использование пространства хранения std::string в каждой итерации цикла, что значительно сокращает количество распределений памяти, которые необходимо выполнить.

Ответ 4

Некоторые из ответов коснулись этого, но я хотел бы подчеркнуть в свете редактирования

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

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

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

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

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

Ответ 5

Эта псевдокодная функция:

not-void function-name () {
    do-something
    return value;
}

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


иначе идентичная псевдокодная функция:

void function-name (not-void& arg) {
    do-something
    arg = value;
}

было бы полезно, если бы у нас был другой метод, смягчающий значение той же переменной, что и нам, и нам нужно сохранить изменения, внесенные в переменную одним из вызовов.

void another-function-name (not-void& arg) {
    do-something
    arg = value;
}

Ответ 6

Мы не можем быть на 100% общими, потому что разные платформы имеют разные ABI, но я думаю, что мы можем сделать некоторые довольно общие утверждения, которые будут применяться в большинстве реализаций с предостережением, что эти вещи в основном применяются к функциям, которые не включены.

Сначала рассмотрим примитивные типы. На низком уровне параметр pass by reference реализуется с использованием указателя, тогда как примитивные возвращаемые значения обычно передаются буквально в регистры. Поэтому возвращаемые значения, скорее всего, будут работать лучше. На некоторых архитектурах это относится и к небольшим структурам. Копирование значения, достаточно маленького, чтобы вписаться в регистр или два, очень дешево.

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

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

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

Ответ 7

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

Я бы рекомендовал не передавать ссылки const, если у вас нет веских оснований. Используйте возвращаемое значение (например, функции сортировки tryGet()).

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