Ответ 1
Я не могу говорить для каждого компилятора, но общий ответ не. Это не сделает эту оптимизацию.
См. GOTW # 81, чтобы прочитать о том, как кастинг на const
в С++ не влияет на оптимизацию, как могут подумать некоторые.
Рассмотрим следующее:
struct Point {double x; double y;};
double complexComputation(const& Point p1, const Point& p2)
{
// p1 and p2 used frequently in computations
}
Могут ли компиляторы оптимизировать передачу по ссылке в pass-by-copy для предотвращения частых разыменований? Другими словами, преобразуйте complexComputation
в это:
double complexComputation(const& Point p1, const Point& p2)
{
double x1 = p1.x; double x2 = p2.x;
double y1 = p1.y; double y2 = p2.y;
// x1, x2, y1, y2 stored in registers and used frequently in computations
}
Так как Point является POD, побочный эффект не может быть вызван копированием за вызывающим абонентом, верно?
Если это случай, то я всегда могу просто передавать объекты POD по ссылке const, независимо от того, насколько малы и не нужно беспокоиться о оптимальной проходящей семантике. Правильно?
EDIT: Меня интересует компилятор GCC, в частности. Я думаю, мне, возможно, придется написать некоторый тестовый код и посмотреть на ASM.
Я не могу говорить для каждого компилятора, но общий ответ не. Это не сделает эту оптимизацию.
См. GOTW # 81, чтобы прочитать о том, как кастинг на const
в С++ не влияет на оптимизацию, как могут подумать некоторые.
Ваш компилятор может абсолютно поднять переменные Point-члена в регистры, если это необходимо. Это, однако, не то же самое, что компилятор, преобразовывающий сам вызов функции в pass по значению.
Вы должны проверить сгенерированную сборку, чтобы узнать, какие оптимизации выполняются.
И FWIW, общее правило, которое я использую, состоит в том, чтобы передать все примитивные типы по значению и всем классам /UDT (POD или не) с помощью ссылки на const, когда я могу, и пусть компилятор разбирает лучшее, что нужно сделать. Мы не должны беспокоиться о деталях того, что делает компилятор, это намного умнее нас.
Есть 2 вопроса.
Во-первых, компилятор не будет преобразовывать pass-by-ref в pass-by-value, особенно если complexComputation
не static
(т.е. может использоваться внешними объектами).
Причина совместимости с API. Для CPU нет такой вещи, как "ссылка". Компилятор преобразует ссылки на указатели. Параметры передаются в стек или через регистр, поэтому код, вызывающий complexComputation
, скорее всего, будет вызываться как (предположим, что double
имеет длину 4 на мгновение):
str x1, [r7, #0x20]
str y1, [r7, #0x24]
str x2, [r7, #0x50]
str y2, [r7, #0x54]
push r7, #0x20 ; push address of p1 onto the stack
push r7, #0x50 ; push address of p2 onto the stack
call complexComputation
В стек помещается только 8 байтов.
Передача с помощью копии, с другой стороны, выталкивает всю структуру в стек, поэтому код сборки будет выглядеть как
push x1 ; push a copy of p1.x onto the stack
push y1 ; push a copy of p1.y onto the stack
push x2 ; push a copy of p2.x onto the stack
push y2 ; push a copy of p2.y onto the stack
call complexComputation
Обратите внимание, что на этот раз 16 байт помещаются в стек, а контент - это числа, а не указатели. Если complexComputation
изменяет семантику передачи параметров, вход становится мусором, и ваша программа может потерпеть крах.
С другой стороны, оптимизация
double complexComputation(const Point& p1, const Point& p2) {
double x1 = p1.x; double x2 = p2.x;
double y1 = p1.y; double y2 = p2.y;
// x1, x2, y1, y2 stored in registers and used frequently in computations
}
можно легко сделать, поскольку компилятор может распознавать, какие переменные используются очень часто и сохраняйте их в зарезервированных регистрах (например, r4 ~ r13 в архитектуре ARM и многих регистрах sXX/dXX) для быстрого доступа.
В конце концов, если вы хотите знать, что компилятор что-то сделал, вы всегда можете разобрать результирующие объекты и сравнить.