Может ли GCC лучше оптимизировать ситуацию, когда я собираю все за один шаг?

gcc оптимизирует код, когда передаю ему флаг -O2, но мне интересно, как хорошо он может это сделать, если я скомпилирую все исходные файлы в объектные файлы и затем свяжу их потом.

Вот пример:

// in a.h
int foo(int n);

// in foo.cpp
int foo(int n) {
  return n;
}

// in main.cpp
#include "a.h"
int main(void) {
  return foo(5);
}

// code used to compile it all
gcc -c -O2 foo.cpp -o foo.o
gcc -c -O2 main.cpp -o main.o
gcc -O2 foo.o main.o -o executable

Обычно gcc должен inline foo, потому что он имеет небольшую функцию и -O2 включает -finline-small-functions, правильно? Но здесь gcc видит только код foo и main независимо от того, создает ли он объектные файлы, поэтому таких оптимизаций не будет, не так ли? Итак, компиляция, как это, делает код медленнее?

Однако я мог бы также скомпилировать его следующим образом:

gcc -O2 foo.cpp main.cpp -o executable

Будет ли это быстрее? Если нет, будет ли это быстрее?

// in foo.cpp
int foo(int n) {
  return n;
}

// in main.cpp
#include "foo.cpp"
int main(void) {
  return foo(5);
}

Изменить. Я посмотрел на objdump, и его дизассемблированный код показал, что работала только #include "foo.cpp".

Ответ 2

Кажется, что вы заново открыли для себя вопрос о отдельной модели компиляции, которую используют C и С++. Хотя это, безусловно, облегчает требования к памяти (что было важно на момент его создания), он делает это, предоставляя компилятору минимальную информацию, а это означает, что некоторые оптимизации (например, эта) не могут быть выполнены.

Более новые языки с их модульными системами могут предоставить столько информации, сколько необходимо, и мы можем надеяться разорвать эти преимущества, если модули попадут в следующую версию С++...

В то же время простейшая вещь, которую нужно использовать, называется Link-Time Optimization. Идея состоит в том, что вы будете выполнять максимально возможную оптимизацию для каждого модуля TU (Translation Unit) для получения объектного файла, но вы также обогатите традиционный объектный файл (который содержит сборку) с помощью IR (промежуточное представление, используемое компиляторами для оптимизации ) для части или всех функций.

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

Конечно, как и все прогоны оптимизации, это имеет стоимость, поэтому делает более длинной компиляцию. Кроме того, это означает, что для компилятора и компоновщика должна быть выбрана специальная опция для запуска этого поведения, в случае gcc это будет -lto или -O4.

Ответ 3

Поскольку вы используете GCC, вы можете использовать механизм спецификации функции C99 inline. Это из ISO/IEC 9899: 1999.

§ 6.7.4 Спецификаторы функций

Синтаксис

¶1 function-specifier:

      inline

Ограничения

¶2 Спецификаторы функций должны использоваться только в объявлении идентификатора для функции.

¶3 Встроенное определение функции с внешней связью не должно содержать определения изменяемый объект со статическим временем хранения и не должен содержать ссылку на идентификатор с внутренней связью.

¶4 В размещенной среде спецификатор функции inline не должен появляться в объявлении main.

Семантика

¶5 Функция, объявленная с помощью спецификатора функции inline, является встроенной функцией. спецификатор функции может появляться более одного раза; поведение такое же, как если бы оно появилось только один раз. Выполнение функции встроенной функции предполагает, что вызовы функции будут такими, как 118) Степень, в которой такие предложения эффективны, от реализации. 119)

¶6 Любая функция с внутренней связью может быть встроенной функцией. Для функции с внешним привязка, применяются следующие ограничения: если функция объявлена ​​с помощью inlineспецификатор функции, то он также должен быть определен в той же самой единице перевода. Если все объявления области видимости для функции в блоке перевода включают функцию inlineспецификатор без extern, то определение в этой единице перевода является встроенным определение. Встроенное определение не обеспечивает внешнего определения функции, и не запрещает внешнее определение в другой единицы перевода. Встроенное определение обеспечивает альтернативу внешнему определению, которое переводчик может использовать для реализации любой вызов функции в одной и той же единице перевода. Не указано, является ли вызов функция использует встроенное определение или внешнее определение .120)

¶7 ПРИМЕР Объявление встроенной функции с внешней связью может привести либо к внешнему определение или определение, доступное для использования только в пределах единицы перевода. Объявление области файла с extern создает внешнее определение. В следующем примере показана полная единица перевода.

inline double fahr(double t)
{
    return (9.0 * t) / 5.0 + 32.0;
}
inline double cels(double t)
{
    return (5.0 * (t - 32.0)) / 9.0;
}
extern double fahr(double); // creates an external definition
double convert(int is_fahr, double temp)
{
    /* A translator may perform inline substitutions */
    return is_fahr ? cels(temp) : fahr(temp);
}

¶8 Обратите внимание, что определение fahr является внешним определением, поскольку fahr также объявляется с помощью extern, но определение cels является встроенным определением. Поскольку cels имеет внешнюю связь и ссылается, внешнее определение должно появиться в другой единицы перевода (см. 6.9); встроенное определение и внешнее определение различно и может быть использовано для вызова.

118) Используя, например, альтернативу обычному механизму вызова функции, например, "inline подстановка". Встроенная подстановка не является текстовой подстановкой и не создает новую функцию. Поэтому, например, расширение макроса, используемого в теле функции, использует определение, которое оно имело в точке, где появляется тело функции, а не где функция вызывается; а также идентификаторы относятся к объявлениям в области, где происходит тело. Аналогично, функция имеет один адрес, независимо от количества встроенных определений, которые встречаются в дополнение к внешним определение.

119) Например, реализация может никогда не выполнять встроенную подстановку или может выполнять только встроенную заменяет вызовы в области объявления inline.

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


Обратите внимание, что GCC также имел функции inline в C до их стандартизации. Если вам нужна эта нотация, прочтите руководство GCC.