Является ли уровень оптимизации -О3 опасным в g++?

Я слышал из разных источников (хотя в основном из моего коллеги), что компиляция с уровнем оптимизации -O3 в g++ как-то "опасна", и ее следует избегать вообще, если не доказано, что это необходимо.

Это правда, и если да, то почему? Должен ли я просто придерживаться -O2?

Ответ 1

В первые дни gcc (2.8 и т.д.) и во времена egcs, а redhat 2.96-O3 иногда был довольно багги. Но это уже более десяти лет назад, а -O3 не сильно отличается от других уровней оптимизации (в баггичности).

Тем не менее, он имеет тенденцию выявлять случаи, когда люди полагаются на поведение undefined из-за более строгого соблюдения правил и, особенно, угловых случаев, языка (ов).

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

По популярному запросу, здесь добавление:

-O3 и особенно дополнительные флаги, такие как -funroll-loops (не активированные -O3), иногда могут приводить к генерации большего количества машинного кода. При определенных обстоятельствах (например, на процессоре с исключительно небольшим кэшем команд L1) это может привести к замедлению из-за всего кода, например. некоторая внутренняя петля теперь больше не подходит для L1I. Как правило, gcc пытается не создавать столько кода, но поскольку он обычно оптимизирует общий случай, это может произойти. Параметры, особенно подверженные этому (например, разворачивание цикла), обычно не включаются в -O3 и соответственно помечены на man-странице. Как правило, рекомендуется использовать -O3 для генерации быстрого кода и возвращаться обратно к -O2 или -O (который пытается оптимизировать размер кода), когда это необходимо (например, когда профайлер указывает пропуски L1I).

Если вы хотите сделать оптимизацию в крайнем случае, вы можете настроить gcc через --param расходы, связанные с определенными оптимизациями. Кроме того, обратите внимание, что gcc теперь имеет возможность добавлять атрибуты в функции, которые управляют настройками оптимизации только для этих функций, поэтому, когда вы обнаружите, что у вас есть проблема с -O3 в одной функции (или вы хотите попробовать специальные флаги только для этой функции) вам не нужно компилировать весь файл или даже весь проект с помощью O2.

otoh, кажется, что нужно соблюдать осторожность при использовании -Ofast, который гласит:

-Ofast обеспечивает все оптимизации -O3. Он также позволяет оптимизировать, которые недействительны для всех стандартных совместимых программ.

что заставляет меня прийти к выводу, что -O3 предназначен для полного соответствия стандартам.

Ответ 2

Это уже сказано в ответе Нила, но не ясно или достаточно сильно:

В моем несколько проверенном опыте применение -O3 для всей программы почти всегда делает ее медленнее (относительно -O2), потому что она включает агрессивный цикл разворачивания и вставки, которые делают программу больше не вписываются в кеш команд. Для больших программ это также может быть справедливо для -O2 относительно -Os!

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

Ответ 3

Опция

-O3 включает более дорогие оптимизации, такие как функция inlining, в дополнение ко всем оптимизациям нижних уровней -O2 и -O1. Уровень оптимизации "-O3" может увеличить скорость результирующего исполняемого файла, но также может увеличить его размер. Под в некоторых случаях, когда эти оптимизации не благоприятны, этот вариант может фактически сделать программу более медленной.

Ответ 4

Некоторое время назад я столкнулся с оптимизацией. Была карта PCI, которая представляла ее регистры (для команды и данные) ячейкой памяти. Мой драйвер просто сопоставил физический адрес этой памяти с указателем уровня приложения и передал ее вызываемому процессу, который работал с ним следующим образом:

unsigned int * pciMemory;
askDriverForMapping( & pciMemory );
...
pciMemory[ 0 ] = someCommandIdx;
pciMemory[ 0 ] = someCommandLength;
for ( int i = 0; i < sizeof( someCommand ); i++ )
    pciMemory[ 0 ] = someCommand[ i ];

Я удивилась, почему карта не действовала так, как ожидалось. И только когда я увидел ассемблер, я понял, что компилятор написал только someCommand[ the last ] в pciMemory, опуская все предыдущие записи.

В заключение: быть точным и внимательным с оптимизацией)))

Ответ 5

Да, O3 более грубо. Я разработчик компилятора, и я обнаружил явные и очевидные ошибки gcc, вызванные генерацией O3 при сбое инструкций по сборке SIMD при создании моего собственного программного обеспечения. Из того, что я видел, большинство программных продуктов поставляется с O2, что означает, что O3 получит меньше внимания на тестирование и исправления ошибок.

Подумайте об этом так: O3 добавляет больше преобразований поверх O2, что добавляет больше преобразований поверх O1. Статистически говоря, больше преобразований означает больше ошибок. Это верно для любого компилятора.