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

С++: почему значение pass-by-value обычно более эффективно, чем pass-by-reference для встроенных (например, C-подобных) типов

так же, как указано в заголовке

4b9b3361

Ответ 1

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

Поставщик компилятора может отказаться игнорировать ссылки на const (а иногда и неконстантные ссылки), когда они используются во встроенных типах - все зависит от информации, доступной компилятору, когда она имеет дело с функцией и ее вызывающими,

Ответ 2

Для типов пакетов, таких как int, char, short и float, размер данных имеет тот же размер (или меньше), чем адрес, переданный для ссылки на фактические данные. Поиск значения на указанном адресе является ненужным шагом и добавляет дополнительные затраты.

Например, возьмите следующие функции foo и bar

void foo(char& c) {...}
void bar(char c) {...}

Когда foo вызывается, адрес передается по значению 32 бит или 64 бит, в зависимости от вашей платформы. Когда вы используете c внутри foo, у вас есть стоимость поиска данных, хранящихся на переданном адресе.

При вызове bar передается значение размера char и нет служебных ресурсов поиска адресов.

Ответ 3

На практике реализации С++ обычно реализуют pass-by-reference, передавая указатель под капотом (предполагая, что вызов не встроен).

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

int foo(const int &a, int *b) {
    int c = a;
    *b = 2;
    return c + a;
}

Для всего, что знает компилятор, b указывает на a, который называется "aliasing". Если бы a передавалось по значению, эта функция могла бы оптимизироваться до эквивалента *b = 2; return 2*a;. В современном конвейере инструкций CPU это может быть больше похоже на "начало загрузки, запуск b, сохранение, ожидание загрузки, умножение на 2, ожидание b для сохранения, возврата", вместо "начать загрузку, начать с сохранения", дождитесь загрузки, дождитесь, когда b будет сохранен, начните загрузку, дождитесь загрузки, добавьте a в c, return ", и вы начнете понимать, почему потенциал для сглаживания может существенно повлиять на производительность. В некоторых случаях, если не обязательно, огромный эффект в этом.

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