Ответ 1
Возможно, вы ищете Оптимизация времени соединения (LTO), а также оптимизацию всей программы.
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"
.
Возможно, вы ищете Оптимизация времени соединения (LTO), а также оптимизацию всей программы.
Кажется, что вы заново открыли для себя вопрос о отдельной модели компиляции, которую используют C и С++. Хотя это, безусловно, облегчает требования к памяти (что было важно на момент его создания), он делает это, предоставляя компилятору минимальную информацию, а это означает, что некоторые оптимизации (например, эта) не могут быть выполнены.
Более новые языки с их модульными системами могут предоставить столько информации, сколько необходимо, и мы можем надеяться разорвать эти преимущества, если модули попадут в следующую версию С++...
В то же время простейшая вещь, которую нужно использовать, называется Link-Time Optimization. Идея состоит в том, что вы будете выполнять максимально возможную оптимизацию для каждого модуля TU (Translation Unit) для получения объектного файла, но вы также обогатите традиционный объектный файл (который содержит сборку) с помощью IR (промежуточное представление, используемое компиляторами для оптимизации ) для части или всех функций.
Когда компоновщик будет вызываться для объединения этих объектных файлов вместе, а не просто слияние файлов вместе, он объединит IR-представления, повторно проведет ряд проходов оптимизации (постоянное распространение, вложение,...), а затем создаст сборка сама по себе. Это означает, что вместо того, чтобы быть просто компоновщиком, он фактически является бэкэнд-оптимизатором.
Конечно, как и все прогоны оптимизации, это имеет стоимость, поэтому делает более длинной компиляцию. Кроме того, это означает, что для компилятора и компоновщика должна быть выбрана специальная опция для запуска этого поведения, в случае gcc это будет -lto
или -O4
.
Поскольку вы используете 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.