В теории по ссылке эффективнее т.к. передача всего одного указателя вместо двух.
В интернетах пишут, что незачем заморачиваться класс и так маленький и по значению сойдёт.
Что думаете ?
Здравствуйте, _NN_, Вы писали:
_NN>В теории по ссылке эффективнее
Определи, что значит "эффективнее".
Например, код не будет быстрее. И даже можно утверждать, что часто будет медленнее. Ведь передача по ссылке запрещает много полезных оптимизаций, например из-за правил алиасинга в C++: https://godbolt.org/z/nso76I .
_NN>т.к. передача всего одного указателя вместо двух.
Ну да. А заодно потребует от вызываемой функции материализовать экземпляр, ссылку на который нужно будет передать в вызываемую функцию.
Разменяли запись в регистр на аллокацию объекта в памяти. Аллокация, конечно, в стеке, но всё равно такой курс размена обычно невыгодный.
_NN>Что думаете ?
Если нет веских причин передавать по ссылке, то передавать string_view всегда нужно по значению. Веские причины встречаются редко.
Здравствуйте, watchmaker, Вы писали:
_NN>>В теории по ссылке эффективнее W>Определи, что значит "эффективнее". W>Например, код не будет быстрее. И даже можно утверждать, что часто будет медленнее. Ведь передача по ссылке запрещает много полезных оптимизаций, например из-за правил алиасинга в C++: https://godbolt.org/z/nso76I .
В этом примере как раз sum_chars_ref гораздо короче
Здравствуйте, _NN_, Вы писали:
W>>Например, код не будет быстрее. И даже можно утверждать, что часто будет медленнее. Ведь передача по ссылке запрещает много полезных оптимизаций, например из-за правил алиасинга в C++: https://godbolt.org/z/nso76I . _NN>В этом примере как раз sum_chars_ref гораздо короче :))
Удивительно, но длина машинного кода не является единственным определяющим фактором времени его работы :)
Важно, что чтений из памяти sum_chars_ref делает вдвое больше. И плюс всякие оптимизации, завязанные на знание числа итераций цикла (unroll, vectorize), отпадают.
То что он короче — это следствие того, что компилятор сдался и просто признал, что его знаний не хватит чтобы понять как такую фигню заставить быстро работать.
Здравствуйте, _NN_, Вы писали:
_NN>В теории по ссылке эффективнее т.к. передача всего одного указателя вместо двух. _NN>В интернетах пишут, что незачем заморачиваться класс и так маленький и по значению сойдёт. _NN>Что думаете ?
1. Есть CppCoreGuidelines описывающий что передавать по ссылке, а что по значению
и string_view как раз попадает под категорию "cheap", так как два машинных слова.
2. Вообще передача по ссылке выглядит безумием на x86-64,
вот мы вычислили string_view и у нас есть два значения (указатель и длина),
в двух регистрах, вместо того, чтобы сразу вызывать (перейти по адресу)
вызываемой функции, мы кладем эти регистры в стек, записываем адрес
(куда положили) в регистр, вызываем функцию, та чтобы работать с string_view
достает данные из стека, загружая их в два регистра.
Безумие какое-то получается.
3. Ну aliasing как ту уже заметили (и он даже работает если всего один аргумент типа const string_view &),
например в середине функции, которой передали "const string_view &" вызывается какая-то функцию,
постэфеккты которой компилятор не может предсказать, компилятор тогда сгенерирует код,
который перечитает string_view из памяти, после вызова этой функции, а то вдруг она поменяла значение.
Здравствуйте, watchmaker, Вы писали:
W>Удивительно, но длина машинного кода не является единственным определяющим фактором времени его работы W>Важно, что чтений из памяти sum_chars_ref делает вдвое больше. И плюс всякие оптимизации, завязанные на знание числа итераций цикла (unroll, vectorize), отпадают.
Очень заинтриговал! А не можешь немного рассказать почему это:
Здравствуйте, reversecode, Вы писали:
R>потому что в конвеер грузится сразу доступ к 4 елементам в случае если длинна кратна 4 R>а в цикле ниже только по одному R>и это медленее для цпу
Спасибо, да, разумно.
R>вообще странно слышать такой вопрос от вас R>кем вы в лк работали ?
Странно? Да ни сколько, работа в ЛК совсем не значит что ты занимаешься оптимизациями В ЛК я был "Mac Applications Development Group Manager".
Здравствуйте, watchmaker, Вы писали:
W>Если нет веских причин передавать по ссылке, то передавать string_view всегда нужно по значению. Веские причины встречаются редко.
Убогое АБИ — веская причина? Виндуза не позволяет передавать такие вещи по значению.
Нет такой подлости и мерзости, на которую бы не пошёл gcc ради бессмысленных 5% скорости в никому не нужном синтетическом тесте
Здравствуйте, T4r4sB, Вы писали:
TB>Здравствуйте, watchmaker, Вы писали:
W>>Если нет веских причин передавать по ссылке, то передавать string_view всегда нужно по значению. Веские причины встречаются редко.
TB>Убогое АБИ — веская причина? Виндуза не позволяет передавать такие вещи по значению.
Можете пояснить при чём тут Виндоуз и где например Линукс лучше в данном случае ?
Здравствуйте, _NN_, Вы писали:
_NN>Можете пояснить при чём тут Виндоуз и где например Линукс лучше в данном случае ?
Попробуй передать структурку по значению, собери ллвм под винду-64 для наглядности, посмотри в код и огорчись)))
Это мусрософт такую хренатень придумал.
Нет такой подлости и мерзости, на которую бы не пошёл gcc ради бессмысленных 5% скорости в никому не нужном синтетическом тесте
Здравствуйте, _NN_, Вы писали:
_NN>Здравствуйте, watchmaker, Вы писали:
_NN>А что насчёт возврата ? _NN>Применимы ли тут аналогичные выводы ? _NN>Судя по дисассемблеру как раз возврат ссылки эффективнее будет:
_NN>
Так ведь метод же вызвали не просто так, а с string_view
будут работать, а для этого в первом случае загрузят данные из памяти
в два регистра, а во втором просто начнут использовать эти два регистра.
Разве не очевидно какой вариант быстрее?
Здравствуйте, T4r4sB, Вы писали:
TB>Здравствуйте, _NN_, Вы писали:
_NN>>Можете пояснить при чём тут Виндоуз и где например Линукс лучше в данном случае ?
TB>Попробуй передать структурку по значению, собери ллвм под винду-64 для наглядности, посмотри в код и огорчись))) TB>Это мусрософт такую хренатень придумал.
Вы про ABI x64 винды ?
Aggregates (other) By pointer. First 4 parameters passed as pointers in RCX, RDX, R8, and R9
Здравствуйте, _NN_, Вы писали:
_NN>Вы про ABI x64 винды ? _NN>Aggregates (other) By pointer. First 4 parameters passed as pointers in RCX, RDX, R8, and R9
Да-да-да. У вас не получится передать стрингвью по значению, в ШИНДОШЫ за вас всё решили и избавили вас от моральных страданий.
Нет такой подлости и мерзости, на которую бы не пошёл gcc ради бессмысленных 5% скорости в никому не нужном синтетическом тесте
Здравствуйте, T4r4sB, Вы писали:
TB> Убогое АБИ — веская причина? Виндуза не позволяет передавать такие вещи по значению.
Возможность передавать структуры по значению — это свойство языка, а не ABI.
Наверное в вышепроцитированном утверждении какое-то слово пропущено (например, "через регистры" или что-то ещё)
TB>Попробуй передать структурку по значению, собери ллвм под винду-64 для наглядности, посмотри в код и огорчись)))
Если сравнивать win c win, то вроде получается не хуже: https://godbolt.org/z/af28Dy
Сходу видна только проблема с тем, что если объект не модифицируется, а передаётся дальше как есть (как в последней паре функций с рекурсией), то получается плохо с копированием его на стек на каждой итерации. Но и для systemV abi аналогичный пример можно придумать (да на самом деле даже этот подойдёт).
Здравствуйте, watchmaker, Вы писали:
W>Возможность передавать структуры по значению — это свойство языка, а не ABI.
Проблема всех компиляторов С/С++ под винду-64, так лучше?
W>Наверное в вышепроцитированном утверждении какое-то слово пропущено (например, "через регистры" или что-то ещё)
TB>>Попробуй передать структурку по значению, собери ллвм под винду-64 для наглядности, посмотри в код и огорчись))) W>Если сравнивать win c win, то вроде получается не хуже: https://godbolt.org/z/af28Dy W>А ты про какой сценарий говоришь?
Ой, там асм. Я уже разучился его понимать. В ЛЛВМ лучше видно. Что передача по значению превращается в передачу по структуре.
Нет такой подлости и мерзости, на которую бы не пошёл gcc ради бессмысленных 5% скорости в никому не нужном синтетическом тесте
И? Заменяем теперь сигнатуру функции на void print(const string_view& sv) и видим, что в IR лучше не стало.
То есть с точки зрения IR кода тут нет выгоды, если мы будет передавать по ссылке const string_view& sv.
А вообще на IR тут смотреть плохо. Он не показывает важные машинно-специфичные оптимизации. Например, передача по ссылке или по значению влияет на register pressure, но в IR это никак не отображено — в нём же бесконечное количество регистров
Здравствуйте, watchmaker, Вы писали:
W>И? Заменяем теперь сигнатуру функции на void print(const string_view& sv) и видим, что в IR лучше не стало.
Вот именно! IR никак не меняется от замены указателя на значение!
W>А вообще на IR тут смотреть плохо. Он не показывает важные машинно-специфичные оптимизации. Например, передача по ссылке или по значению влияет на register pressure, но в IR это никак не отображено — в нём же бесконечное количество регистров
Неважно, он показывает, что в функцию передаётся именно указатель. По-моему, этого достаточно.
Нет такой подлости и мерзости, на которую бы не пошёл gcc ради бессмысленных 5% скорости в никому не нужном синтетическом тесте
Здравствуйте, T4r4sB, Вы писали:
TB>Здравствуйте, watchmaker, Вы писали:
W>>И? Заменяем теперь сигнатуру функции на void print(const string_view& sv) и видим, что в IR лучше не стало. :))
TB>Вот именно! IR никак не меняется от замены указателя на значение!
Хорошо что мы в этом согласны :)
Значит эта ветка всё же показывает, что тут нет веских причин передавать string_view по ссылке даже под win. Так?
Напомню, с чего всё началось: http://rsdn.org/forum/cpp/7455853.1
TB>Неважно, он показывает, что в функцию передаётся именно указатель. По-моему, этого достаточно.
в результирующем асм коде никакого указателя нет даже в вашем случае
компилятор оптимизирует и передает в двух регистрах вашу пару хранящуюся в структуре
W>Значит эта ветка всё же показывает, что тут нет веских причин передавать string_view по ссылке даже под win. Так? W>Напомню, с чего всё началось: http://rsdn.org/forum/cpp/7455853.1
Здравствуйте, reversecode, Вы писали:
R>в результирующем асм коде никакого указателя нет даже в вашем случае R>компилятор оптимизирует и передает в двух регистрах вашу пару хранящуюся в структуре
Компилятор передаёт указатель на структуру снаружи и разыменовывает её внутри. Не, я понимаю, есть оптимизации между границами функций (тогда вообще пофиг как передавать, компилятор разберётся), но если функция сидит в другом бинарнике, например, то эта оптимизация не сработает.
Нет такой подлости и мерзости, на которую бы не пошёл gcc ради бессмысленных 5% скорости в никому не нужном синтетическом тесте
TB>Компилятор передаёт указатель на структуру снаружи и разыменовывает её внутри. Не, я понимаю, есть оптимизации между границами функций (тогда вообще пофиг как передавать, компилятор разберётся), но если функция сидит в другом бинарнике, например, то эта оптимизация не сработает.
вам надо посмотреть видео доклада антона полухина про С++ строки, и вывод именно такой — не надо быть умнее компилятора
не важно где функция, компилятор увидит передающийся тип и как он используется в теле
и с оптимизирует
Здравствуйте, _NN_, Вы писали:
_NN>В теории по ссылке эффективнее т.к. передача всего одного указателя вместо двух. _NN>В интернетах пишут, что незачем заморачиваться класс и так маленький и по значению сойдёт. _NN>Что думаете ?
При передаче по значению его можно модифицировать внутри функции, а не копировать (если по константной ссылке).
TB>>Неважно, он показывает, что в функцию передаётся именно указатель. По-моему, этого достаточно.
R>в результирующем асм коде никакого указателя нет даже в вашем случае R>компилятор оптимизирует и передает в двух регистрах вашу пару хранящуюся в структуре
Под какую платформу? В этой ветке T4r4sB рассказывает про windows.
А windows ABI это явно запрещает:
A single argument is never spread across multiple register
Здравствуйте, T4r4sB, Вы писали:
TB>Ну как сказать. Убогое АБИ — это веская причина того, что ты в принципе не сможешь передать св по значению. Согласен?
Вот прямо с такой формулировкой? Совсем не согласен!
Возможность передать по значению — это свойство языка C++. От используемого ABI оно не зависит. Даже если используется абстрактная машина C++ и никакого ABI нет вообще.
Этот код будет передавать аргументы вне зависимости от ABI.
ABI будет задавать лишь куда аргументы будет помещены: например, будут ли они переданы через регистры или переданы через стек, или каким-то ещё невообразимым образом. Но во всех случаях они будут переданы по значению. "Через регистры по значению", "через стек по значению", "невообразимым образом, но по значению", и так далее.
Здравствуйте, watchmaker, Вы писали:
W>Вот прямо с такой формулировкой? Совсем не согласен! W>Возможность передать по значению — это свойство языка C++. От используемого ABI оно не зависит. Даже если используется абстрактная машина C++ и никакого ABI нет вообще.
В контексте низкоуровневых оптимизаций говорить об абстракциях неуместно.
Нет такой подлости и мерзости, на которую бы не пошёл gcc ради бессмысленных 5% скорости в никому не нужном синтетическом тесте
Здравствуйте, watchmaker, Вы писали:
W>ABI будет задавать лишь куда аргументы будет помещены: например, будут ли они переданы через регистры или переданы через стек, или каким-то ещё невообразимым образом. Но во всех случаях они будут переданы по значению. "Через регистры по значению", "через стек по значению", "невообразимым образом, но по значению", и так далее.
Мы тут обсуждаем именно подкапотную работу, а не абстракции языка.
То, что всё передается по значению по умолчанию это свойства языка и никто с этим не спорит, вопрос будет ли это достаточно эффективным в случае string_view или нет.
Здравствуйте, T4r4sB, Вы писали:
TB>Здравствуйте, watchmaker, Вы писали:
W>>Вот прямо с такой формулировкой? Совсем не согласен! W>>Возможность передать по значению — это свойство языка C++. От используемого ABI оно не зависит. Даже если используется абстрактная машина C++ и никакого ABI нет вообще.
TB>В контексте низкоуровневых оптимизаций говорить об абстракциях неуместно.
И это говорит человек, который не спускается ниже IR при разговорах об оптимизации!
Ладно, если серьёзно, то вопрос в первом сообщении темы был про передачу аргументов std::string_view: по ссылке это делать или по значению.
Понятно, что есть кривые ABI, где не будет разницы в сгенированном машинном коде при вызове функции. Спасибо, что напомнил об этом (я вот под windows давно не пишу и честно об этом забыл). Но это лишь означает, что их наличие не сдвигает чашу весов в сторону одного из вариантов. То есть их наличие — не причина предпочитать const std::string_view против const std::string_view& или наоборот.
А вот то, что при const std::string_view компилятору разрешено сделать больше оптимизаций, чем при const std::string_view& (даже на архитектурах, где передача идёт через память, а код вызова одинаков с точностью до интструкции), — уже причина.
И то, что на systemV ABI передача через значение чаще эффективнее — тоже причина (хотя не такая хорошая, как предыдущая).
https://godbolt.org/z/uEKEED
под виндовс какой именно компиль ?
кстати по линку выше видно что если передавать по ссылке
то происходит два дополнительных чтения с памяти в цикле
так то всяко разно передача по значению будет быстрей
Здравствуйте, watchmaker, Вы писали:
W>Да. А ещё интересно, что даже если заменить std::string_view, скажем, на int или на void*, то всё равно не собирается: W>Наверное, дело тут ошибка всё же не в std::string_view
Что значит ошибка в коде ?
Код компилируется с ссылкой успешно, значит в нём нет ошибки.
Здравствуйте, reversecode, Вы писали:
R>https://godbolt.org/z/uEKEED R>под виндовс какой именно компиль ?
Ну msvc вроде не умеет там под что-то отличное копмилировать, так что он подойдёт. А clang и gcc там собирают под unix, но для экспериментов их можно попросить использовать чужеродное abi.
R>кстати по линку выше видно что если передавать по ссылке R>то происходит два дополнительных чтения с памяти в цикле R>так то всяко разно передача по значению будет быстрей
Всё так.
И это платформонезависимая оптимизация. То есть для её не важно какое abi используется, и вообще linux ли там или windows. Сама её возможность следует из описания абстрактной машины C++.
Ведь если std::string_view передаётся по ссылке, то компилятор будет обязан на каждой итерации перечитывать значение sv.size() из памяти. Например, из-за того, что передача по ссылке не запрещает ссылаться для обоих аргументов функции на одну и ту же память (а значит модификация sum может поменять значение str.size(), а следовательно нужно перечитать str).
Строго говоря, конечно, компилятор может сгенерировать две ветки в функции: в одной делать честно и тупо, а в другой проверять, что все аргументы не пересекаются по памяти и сводить его к случаю как будто аргумент передан по значению. Но для мало-мальски сложных функций это очень сложно сделать на практике (например, сложно доказать, что какой-нибудь callback не поменяет память в string_view, ссылка на которую была передана в функцию).
То есть тут передача по значению играет роль модификатора restrict из C: она гарантирует, что аргументы ссылаются на разную память, и из-за этого её можно эффективно кешировать и отслеживать изменения в ней.
Здравствуйте, _NN_, Вы писали:
_NN>Здравствуйте, Zhendos, Вы писали:
Z>>Разве не очевидно какой вариант быстрее?
_NN>Вот выхлоп MSVC. _NN>Как видно передача по значению занимается копированием, а по ссылке нет. _NN>Неужели вариант с копированием будет быстрее ?
Вы куда-то контекст обсуждения потеряли.
Естественно функция которую не вызывают вообще самая быстрая,
можно считать что она вызывается за 0 тактов.
Но мы же хотим чтобы в вся программа при использовании того или иного
способа возврата string_view работала быстрее.
Поэтому нужно рассматривать функцию возвращающую string_view вместе
с кодом который ее вызывает.
И я вижу два варианта:
string_view_ref_or_value get_string_view();
string_view_ref_or_value x = get_string_view();
либо мы сразу используем x, тогда мы лезем в память и достаем
и data и size (1), или передаем полученный x куда-то, но как мы выяснили
передачу лучше делать по значению, значит мы снова лезем в память и опять же достаем
data и size (2) (можно конечно дальше передавать ссылку, что кстати очень опасно,
но в конце концов мы придем к 1 или 2).
В обоих случаях получается что либо одинаково быстро будет
работать вся программа или чуть медленнее, если string_view вычисляется,
потому кладется в поле класса, а потом на поле класса возвращается ссылка.
Z>Вы куда-то контекст обсуждения потеряли. Z>Естественно функция которую не вызывают вообще самая быстрая, Z>можно считать что она вызывается за 0 тактов. Z>Но мы же хотим чтобы в вся программа при использовании того или иного Z>способа возврата string_view работала быстрее. Z>Поэтому нужно рассматривать функцию возвращающую string_view вместе Z>с кодом который ее вызывает.
Z>И я вижу два варианта:
Z>
Z>string_view_ref_or_value get_string_view();
Z>string_view_ref_or_value x = get_string_view();
Z>
Z>либо мы сразу используем x, тогда мы лезем в память и достаем Z>и data и size (1), или передаем полученный x куда-то, но как мы выяснили Z>передачу лучше делать по значению, значит мы снова лезем в память и опять же достаем Z>data и size (2) (можно конечно дальше передавать ссылку, что кстати очень опасно, Z>но в конце концов мы придем к 1 или 2).
А если мы передаём одни ссылки то нам не нужно доставать data и size до функции, где реально они будут нужны.
Скажем, в цепочке f->g->h->i мы можем создать объект в f и передавать ссылку до i, и только там достать data и size;
Если передавать по значению получается нужно их доставать и передавать каждый раз.
Или есть подвох ?
Z>В обоих случаях получается что либо одинаково быстро будет Z>работать вся программа или чуть медленнее, если string_view вычисляется, Z>потому кладется в поле класса, а потом на поле класса возвращается ссылка.
Т.е. если возвращать заранее созданный string_view как в случае поле класса то ссылка будет эффективней чем возвращать по значению ? Так ?
Z>>string_view_ref_or_value get_string_view();
Z>>string_view_ref_or_value x = get_string_view();
Z>>
Z>>либо мы сразу используем x, тогда мы лезем в память и достаем Z>>и data и size (1), или передаем полученный x куда-то, но как мы выяснили Z>>передачу лучше делать по значению, значит мы снова лезем в память и опять же достаем Z>>data и size (2) (можно конечно дальше передавать ссылку, что кстати очень опасно, Z>>но в конце концов мы придем к 1 или 2). _NN>А если мы передаём одни ссылки то нам не нужно доставать data и size до функции, где реально они будут нужны. _NN>Скажем, в цепочке f->g->h->i мы можем создать объект в f и передавать ссылку до i, и только там достать data и size; _NN>Если передавать по значению получается нужно их доставать и передавать каждый раз. _NN>Или есть подвох ?
Ничего не понял,
если есть цепочка возврата, то какая разница заняты два регистра или один?
каждый раз это просто "ret", зачем кого-то доставать?
"f" заполняет два регистра и вызвает "ret", "g" уже имеет правильно заполненные регистры,
поэтому она просто вызывает ret, как и "h" и "i" сразу их использует.
Z>>В обоих случаях получается что либо одинаково быстро будет Z>>работать вся программа или чуть медленнее, если string_view вычисляется, Z>>потому кладется в поле класса, а потом на поле класса возвращается ссылка.
_NN>Т.е. если возвращать заранее созданный string_view как в случае поле класса то ссылка будет эффективней чем возвращать по значению ? Так ?
Нет, будет так же или хуже при возврате по ссылке. Хуже потому что если мы где-то будет иметь код
вида:
то так как мы получили "s" по ссылке, то мы два раза перечитаем "data" и "size"
из памяти, так как может global_function поменяла данные по ссылке.
То есть если мы предаем не используя, то есть у нас прямая цепочка
h() { return g(); } g() { return f(); }
то будет также, потому что хотя мы используем два регистра,
а не один, но это ведь "конец" всех этих функций,
и регистры не нужно на стек сохранять,
а если мы между делом, что-то делаем с этим 'const string_view&' то будет хуже.
И как бы нужно не забывать, что string_view это по сути ссылка,
и она может быть получена от std::string,
и чем дальше мы отдаем string_view, тем сложнее следить за временем жизни,
передавать на несколько уровней вверх без использования string_view это
по-моему очень странный сценарий.
Здравствуйте, Zhendos, Вы писали:
Z>то будет также, потому что хотя мы используем два регистра, Z>а не один, но это ведь "конец" всех этих функций, Z>и регистры не нужно на стек сохранять, Z>а если мы между делом, что-то делаем с этим 'const string_view&' то будет хуже.
MSVC судя по всему не очень доволен передачей по значению.
Получаем копирование из памяти вместо использование регистров:
Здравствуйте, _NN_, Вы писали:
_NN>Здравствуйте, Zhendos, Вы писали:
Z>>то будет также, потому что хотя мы используем два регистра, Z>>а не один, но это ведь "конец" всех этих функций, Z>>и регистры не нужно на стек сохранять, Z>>а если мы между делом, что-то делаем с этим 'const string_view&' то будет хуже.
_NN>MSVC судя по всему не очень доволен передачей по значению.
_NN>https://gcc.godbolt.org/z/bEiWGU
1. Не inline бесмысленна в этом коде, функция должна быть extern,
то есть ее тело должно быть невидимым для компилятор в этой единице трансляции
2. Нужно конечно в godbolt поменять компилятор на любой другой: icc, clang, gcc
Ведь в том ABI в x86-64 который реализовала MS весь спор о передаче по ссылке 16 байт
или по значению, а также возврат по ссылке 16 байт или по значению полностью бессмыслен,
разве это не очевидно?
_NN_:
_NN>Мы тут обсуждаем именно подкапотную работу, а не абстракции языка. _NN>То, что всё передается по значению по умолчанию это свойства языка и никто с этим не спорит, вопрос будет ли это достаточно эффективным в случае string_view или нет.
В случае, когда функция инлайнится, calling conventions ничего не решают.
В случае, когда вызов функции не инлайнится, следует учитывать не только то, каким способом на низком уровне будут переданы аргументы, но ещё и насколько удобно компилятору будет оптимизировать код после такого вызова. Передача аргумента по const reference никак не защищает его от изменения внутри вызываемой функции, т.к. она легально может применить const_cast (если объект изначально не const) и дальше делать с ним что угодно. Когда же локальный объект куда-то передаётся по значению, отследить, что его не модифицируют, заметно проще.