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

Могут ли компиляторы С++ оптимизировать параметры передачи через константные параметры POD в pass by copy?

Рассмотрим следующее:

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.

4b9b3361

Ответ 1

Я не могу говорить для каждого компилятора, но общий ответ не. Это не сделает эту оптимизацию.

См. GOTW # 81, чтобы прочитать о том, как кастинг на const в С++ не влияет на оптимизацию, как могут подумать некоторые.

Ответ 2

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

Вы должны проверить сгенерированную сборку, чтобы узнать, какие оптимизации выполняются.

И FWIW, общее правило, которое я использую, состоит в том, чтобы передать все примитивные типы по значению и всем классам /UDT (POD или не) с помощью ссылки на const, когда я могу, и пусть компилятор разбирает лучшее, что нужно сделать. Мы не должны беспокоиться о деталях того, что делает компилятор, это намного умнее нас.

Ответ 3

Есть 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) для быстрого доступа.


В конце концов, если вы хотите знать, что компилятор что-то сделал, вы всегда можете разобрать результирующие объекты и сравнить.