так же, как указано в заголовке
С++: почему значение pass-by-value обычно более эффективно, чем pass-by-reference для встроенных (например, C-подобных) типов
Ответ 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 ", и вы начнете понимать, почему потенциал для сглаживания может существенно повлиять на производительность. В некоторых случаях, если не обязательно, огромный эффект в этом.
Конечно, сглаживание только препятствует оптимизации в тех случаях, когда оно изменяет эффект функции для некоторого возможного ввода. Но только потому, что ваше намерение для функции состоит в том, что наложение не должно влиять на результаты, это не обязательно означает, что компилятор может предположить, что это не так: иногда на самом деле, в вашей программе нет псевдонимов, но компилятор не делает Не знаю. И необязательно, чтобы второй параметр указателя, в любое время, когда ваша функция вызывает код, который оптимизатор "не видит", он должен предположить, что любая ссылка может измениться.