gcc (GNU C Compiler) - набор утилит для компиляции, ассемблирования и компоновки. Их целью является создание готового к запуску исполняемого файла в формате, понимаемом вашей ОС. Для Linux, этим форматом является ELF (Executable and Linking Format) на x86 (32- и 64-битных). Но знаете ли вы, что могут сделать для вас некоторые параметры gcc? Если вы ищете способы оптимизации получаемого бинарного файла, подготовки сессии отладки или просто наблюдать за действиями, предпринимаемыми gcc для превращения вашего исходного кода в исполняемый файл, то знакомство с этими параметрами обязательно. Так что, читайте.

Напомню, что gcc делает несколько шагов, а не только один. Вот небольшое объяснение их смысла:

    Препроцессирование: Создание кода, более не содержащего директив. Вещи вроде «#if» не могут быть поняты компилятором, поэтому должны быть переведены в реальный код. Также на этой стадии разворачиваются макросы, делая итоговый код больше, чем оригинальный.

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

    Ассемблирование: Принимаются ассемблерные мнемоники и производятся объектные коды, содержащие коды команд. Часто недопонимают то, что на стадии компиляции не производятся коды команд, это делается на стадии ассемблирования. В результате получаются один или более объектных файла, содержащие коды команд, которые являются действительно машинозависимыми.

    Компоновка: Трансформирует объектные файлы в итоговые исполняемые. Одних только кодов операции недостаточно для того, чтобы операционная система распознала и выполнила их. Они должны быть встроены в более полную форму. Эта форма, известная как бинарный формат, указывает, как ОС загружает бинарный файл, компонует перемещение и делает другую необходимую работу. ELF является форматом по умолчанию для Linux на x86.

    Параметры gcc описаны здесь, прямо и косвенно затрагивая все четыре стадии, поэтому для ясности, эта статья построена следующим образом:

    Параметры, относящиеся к оптимизации

    Параметры, относящиеся к вызову функций

    Параметры, относящиеся к отладке

    Параметры, относящиеся к препроцессированию

    Прежде всего, давайте ознакомимся с вспомогательными инструментами, которые помогут нам проникать в итоговый код:

    Коллекция утилит ELF, которая включает в себя такие программы, как objdump и readelf. Они парсят для нас информацию о ELF.

    Степень ошибки = изолированные ошибочные ветви / изолированные ветви

    Теперь вычислим степень ошибки для каждого бинарного файла. Для non-optimized получилось 0,5117%, в то время как optimizedO2 получил 0,4323% - в нашем случае, выгода очень мала. Фактическая выгода может различаться для реальных случаев, так как gcc сам по себе не может много сделать без внешних указаний. Пожалуйста, прочтите о __builtin_expect() в документации по gcc для подробной информации.

GСС - это свободно доступный оптимизирующий компилятор для языков C, C++.

Программа gcc , запускаемая из командной строки, представяляет собой надстройку над группой компиляторов. В зависимости от расширений имен файлов, передаваемых в качестве параметров, и дополнительных опций, gcc запускает необходимые препроцессоры, компиляторы, линкеры.

Файлы с расширением .cc или .C рассматриваются, как файлы на языке C++, файлы с расширением .c как программы на языке C, а файлы c расширением .o считаются объектными.

Чтобы откомпилировать исходный код C++, находящийся в файле F.cc , и создать объектный файл F.o , необходимо выполнить команду:

Gcc -c F.cc

Опция –c означает «только компиляция».

Чтобы скомпоновать один или несколько объектных файлов, полученных из исходного кода - F1.o , F2.o , ... - в единый исполняемый файл F , необходимо ввести команду:

Gcc -o F F1.o F2.o

Опция -o задает имя исполняемого файла.

Можно совместить два этапа обработки - компиляцию и компоновку - в один общий этап с помощью команды:

Gcc -o F F1.cc ... -lg++

- возможные дополнительные опции компиляции и компоновки. Опция –lg++ указывает на необходимость подключить стандартную библиотеку языка С++, - возможные дополнительные библиотеки.
После компоновки будет создан исполняемый файл F, который можно запустить с помощью команды

./F

- список аргументов командной строки Вашей программы.
В процессе компоновки очень часто приходится использовать библиотеки. Библиотекой называют набор объектных файлов, сгруппированных в единый файл и проиндексированных. Когда команда компоновки обнаруживает некоторую библиотеку в списке объектных файлов для компоновки, она проверяет, содержат ли уже скомпонованные объектные файлы вызовы для функций, определенных в одном из файлов библиотек. Если такие функции найдены, соответствующие вызовы связываются с кодом объектного файла из библиотеки. Библиотеки могут быть подключены с помощью опции вида -lname . В этом случае в стандартных каталогах, таких как /lib , /usr/lib, /usr/local/lib будет проведен поиск библиотеки в файле с именем libname.a . Библиотеки должны быть перечислены после исходных или объектных файлов, содержащих вызовы к соответствующим функциям.

Опции компиляции

Среди множества опций компиляции и компоновки наиболее часто употребляются следующие:

Опция Назначение
-c Эта опция означает, что необходима только компиляция. Из исходных файлов программы создаются объектные файлы в виде name.o . Компоновка не производится.
-Dname=value Определить имя name в компилируемой программе, как значение value . Эффект такой же, как наличие строки #define name value в начале программы. Часть =value может быть опущена, в этом случае значение по умолчанию равно 1.
-o file-name Использовать file-name в качестве имени для создаваемого файла.
-lname Использовать при компоновке библиотеку libname.so
-Llib-path
-Iinclude-path
Добавить к стандартным каталогам поиска библиотек и заголовочных файлов пути lib-path и include-path соответственно.
-g Поместить в объектный или исполняемый файл отладочную информацию для отладчика gdb . Опция должна быть указана и для компиляции, и для компоновки. В сочетании –g рекомендуется использовать опцию отключения оптимизации –O0 (см.ниже)
-MM Вывести зависимости от заголовочных файлов, используемых в Си или С++ программе, в формате, подходящем для утилиты make . Объектные или исполняемые файлы не создаются.
-pg Поместить в объектный или исполняемый файл инструкции профилирования для генерации информации, используемой утилитой gprof . Опция должна быть указана и для компиляции, и для компоновки. Собранная с опцией -pg программа при запуске генерирует файл статистики. Программа gprof на основе этого файла создает расшифровку, указывающую время, потраченное на выполнение каждой функции.
-Wall Вывод сообщений о всех предупреждениях или ошибках, возникающих во время компиляции программы.
-O1
-O2
-O3
Различные уровни оптимизации.
-O0 Не оптимизировать. Если вы используете многочисленные -O опции с номерами или без номеров уровня, действительной является последняя такая опция.
-I Используется для добавления ваших собственных каталогов для поиска заголовочных файлов в процессе сборки
-L Передается компоновщику. Используется для добавления ваших собственных каталогов для поиска библиотек в процессе сборки.
-l Передается компоновщику. Используется для добавления ваших собственных библиотек для поиска в процессе сборки.

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

Какие опции в GCC по умолчанию?

(1) По умолчанию в GCC используется уровень оптимизаций “-O0”. Он явно не оптимален с точки зрения производительности и не рекомендуется для компиляции конечного продукта.
GCC не распознает архитектуру, на которой запускается компиляция, пока не передана опция ”-march=native”. По умолчанию GCC использует опцию, заданную при его конфигурации. Чтобы узнать конфигурацию GCC, достаточно запустить:

Это означает что GCC добавит “-march=corei7” к вашим опциям (если не будет указана другая архитектура).
Большинство GCC компиляторов для x86 (базовый для 64 битного Linux) добавляет: “-mtune=generic -march=x86-64” к заданным опциям, так как при конфигурации не были заданы опции, определяющие архитектуру. Вы всегда можете узнать все опции, передаваемые при запуске GCC, а также его внутренние опции при помощи команды:

В итоге, часто используемое:

Указание используемой архитектуры важно для производительности. Единственным исключением можно считать те программы, где вызов библиотечных функций занимает почти все время запуска. GLIBC может выбрать оптимальную для данной архитектуры функцию во время исполнения. Важно отметить, что при статической линковке некоторые GLIBC функции не имеют версий под различные архитектуры. То есть динамическая сборка лучше, если важна быстрота GLIBC функций. .
(2) По умолчанию большинство GCC компиляторов для x86 в 32 битном режиме используют x87 модель вычислений с плавающей точкой, так как были сконфигурированы без “-mfpmath=sse”. Только если GCC конфигурация содержит “--with-mfpmath=sse”:

компилятор будет использовать SSE модель по умолчанию.Во всех остальных случаях лучше добавлять опцию “-mfpmath=sse” к сборке в 32 битном режиме.
Так, часто используемое:

Добавление опции ”-mfpmath=sse” важно в 32 битном режиме! Исключением является компилятор, в конфигурации которого есть “--with-mfpmath=sse".

32 битный режим или 64 битный?

32 битный режим обычно используется для сокращения объема используемой памяти и как следствие ускорения работы с ней (больше данных помещается в кеш).
В 64 битном режиме (по сравнению с 32 битным) количество доступных регистров общего пользования увеличивается с 6 до 14, XMM регистров с 8 до 16. Также все 64 битные архитектуры поддерживают SSE2 расширение, поэтому в 64 битном режиме не надо добавлять опцию “-mfpmath=sse”.
Рекомендуется использовать 64 битный режим для счетных задач, а 32 битный режим для мобильных приложений.

Как получить максимальную производительность?

Определенного набора опций для получения максимальной проивзодительности не существует, однако в GCC есть много опций, которые стоит попробовать использовать. Ниже представлена таблица с рекомендуемыми опциями и прогнозами прироста для процессоров Intel Atom и 2nd Generation Intel Core i7 относительно опции “-O2”. Прогнозы основаны на среднем геометрическом результатов определенного набора задач, скомпилированных GCC версии 4.7. Также предполагается, что конфигурация компилятора была проведена для x86-64 generic.
Прогноз увеличения производительности на мобильных приложениях относительно “-O2” (только в 32 битном режиме, так как он основной для мобильного сегмента):

Прогноз увеличения производительности на вычислительных задачах относительно “-O2” (в 64 битном режиме):
-m64 -Ofast -flto ~17%
-m64 -Ofast -flto -march=native ~21%
-m64 -Ofast -flto -march=native -funroll-loops ~22%

Преимущество 64 битного режима над 32 битным для вычислительных задач при опциях “-O2 -mfpmath=sse” составляет около ~5%
Все данные в статье являются прогнозом, основанном на результатах определенного набора бенчмарков.
Ниже представлено описание используемых в статье опций. Полное описание (на английском): http://gcc.gnu.org/onlinedocs/gcc-4.7.1/gcc/Optimize-Options.html "
  • "-Ofast" аналогично "-O3 -ffast-math" включает более высокий уровень оптимизаций и более агрессивные оптимизации для арифметических вычислений (например, вещественную реассоциацию)
  • "-flto" межмодульные оптимизации
  • "-m32" 32 битный режим
  • "-mfpmath=sse" включает использование XMM регистров в вещественной арифметике (вместо вещественного стека в x87 режиме)
  • "-funroll-loops" включает развертывание циклов

Теперь, когда вы узнали кое-что о стандарте С, давайте рассмотрим опции, которые предлагает компилятор gcc для гарантии соответствия стандарту языка С, на котором вы пишете. Есть три способа, позволяющих убедиться в том, что ваш код на С соответствует стандартам и не содержит изъянов: опции, контролирующие версию стандарта, соответствия с которой вы намерены добиваться, определения, контролирующие заголовочные файлы, и опции предупреждений, инициирующие более строгую проверку программного кода.

У gcc есть огромный набор опций, и здесь мы рассмотрим лишь те из них, которые считаем наиболее важными. Полный перечень опций можно найти на страницах интерактивного справочного руководства gcc. Мы также кратко обсудим некоторые опции директивы #define , которые можно применять; обычно их следует задавать в вашем исходном программном коде перед любыми строками с директивой #include или определять в командной строке gcc. Вас может удивить такое обилие опций для выбора применяемого стандарта вместо простого флага, заставляющего использовать современный стандарт. Причина заключается в том, что много более старых программ полагается на исторически сложившееся поведение компилятора и потребовалась бы значительная работа по их обновлению в соответствии с последними стандартами. Редко, если вообще когда-нибудь, вам захочется обновить компилятор для того, чтобы он начал прерывать работающий программный код. По мере изменения стандартов важно иметь возможность работать вопреки определенному стандарту, даже если это и не самая свежая версия стандарта.

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

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

Опции компилятора для отслеживания стандартов

Ansi - это самая важная опция, касающаяся стандартов и заставляющая компилятор действовать в соответствии со стандартом языка ISO C90. Она отключает некоторые расширения gcc, не совместимые со стандартом, отключает в программах на языке С комментарии в стиле С++ (//) и включает обработку триграфов (трехсимвольных последовательностей) ANSI. Кроме того, она содержит макрос __ STRICT_ANSI__ , который отключает некоторые расширения в заголовочных файлах, не совместимые со стандартом. В последующих версиях компилятора принятый стандарт может измениться.

Std= - эта опция обеспечивает более тонкий контроль используемого стандарта, предоставляя параметр, точно задающий требуемый стандарт. Далее приведены основные возможные варианты:

С89 - поддерживать стандарт C89;

Iso9899:1999 - поддерживать последнюю версию стандарта ISO, C90;

Gnu89 - поддерживать стандарт C89, но разрешить некоторые расширения GNU и некоторые функциональные возможности C99. В версии 4.2 gcc этот вариант применяется по умолчанию.

Опции для отслеживания стандарта в директивах define

Существуют константы (#defines), которые могут задаваться опциями в командной строке или виде определений в исходном тексте программы. Мы, как правило, считаем, что для них используется командная строка компилятора.

STRICT_ANSI__ - заставляет применять стандарт С ISO. Определяется, когда в командной строке компилятора задана опция -ansi .

POSIX_C_SOURCE=2 - активизирует функциональные возможности, определенные стандартами IEEE Std 1003.1 и 1003.2. Мы вернемся к этим стандартам чуть позже в этой главе.

BSD_SOURCE - включает функциональные возможности систем BSD. Если они конфликтуют с определениями POSIX, определения BSD обладают более высоким приоритетом.

GNU_SOURCE - допускает широкий диапазон свойств и функций, включая расширения GNU. Если эти определения конфликтуют с определениями POSIX, у последних более высокий приоритет.

Опции компилятора для вывода предупреждений

Эти опции передаются компилятору из командной строки. И снова мы перечислим лишь основные, полный список можно найти в интерактивном справочном руководстве gcc.

Pedantic - эта наиболее мощная опция проверки чистоты, программного кода на языке С. Помимо включения опции проверки на соответствие стандарту С, она отключает некоторые традиционные конструкции языка С, запрещенные стандартом, и делает недопустимыми все расширения GNU по отношению к стандарту. Эту опцию следует применять, чтобы добиться максимальной переносимости вашего кода на С. Недостаток ее в том, что компилятор сильно озабочен чистотой вашего программного кода, и порой приходится поломать голову для того, чтобы разделаться с несколькими оставшимися предупреждениями.

Wformat - проверяет корректность типов аргументов функций семейства printf .

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

Wswitch-default - проверяет наличие варианта default в операторах switch , что обычно считается хорошим стилем программирования.

Wunused - проверяет разнообразные случаи, например, статические функции объявленные, но не описанные, неиспользуемые параметры, отброшенные результаты.

Wall - включает большинство типов предупреждений gcc, в том числе все предыдущие опции - W (не охватывается только -pedantic). С ее помощью легко добиться чистоты программного кода.

Примечание

Существует еще огромное множество дополнительных опций предупреждений, все подробности см. на Web-страницах gcc. В основном мы рекомендуем применять -Wall ; это удачный компромисс между проверкой, обеспечивающей программный код высокого качества, и необходимостью вывода компилятором массы тривиальных предупреждений, которые становится трудно свести к нулю.