C ++: почему передача по значению, как правило, более эффективна, чем передача по ссылке для встроенных (т. Е. C-подобных) типов - PullRequest
19 голосов
/ 18 марта 2011

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

Ответы [ 3 ]

19 голосов
/ 18 марта 2011

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

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

9 голосов
/ 18 марта 2011

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

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

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

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

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

4 голосов
/ 18 марта 2011

На практике реализации C ++ обычно реализуют передачу по ссылке, передавая указатель изнутри (при условии, что вызов не встроен).

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

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

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

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

...