Чи небезпечний рівень оптимізації -O3 у г ++?


232

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

Це правда, і якщо так, то чому? Чи варто мені просто дотримуватися -O2?


38
Це небезпечно лише в тому випадку, якщо ви покладаєтесь на невизначену поведінку. І навіть тоді я був би здивований, якби рівень оптимізації щось зіпсував.
Сет Карнегі

5
Компілятор все ще обмежений для створення програми, яка поводиться "як би", вона точно склала ваш код. Я не знаю, що -O3вважається особливо глючним? Я думаю, що, можливо, це може зробити не визначене поведінку «гіршим», оскільки це може робити дивні та чудові речі, засновані на певних припущеннях, але це буде вашою власною виною. Тож загалом я б сказав, що це добре.
BoBTFish

5
Це правда, що більш високі рівні оптимізації більш схильні до помилок компілятора. Я сам потрапив у декілька випадків, але загалом вони все ще досить рідкісні.
Містичний

21
-O2вмикається -fstrict-aliasing, і якщо ваш код вижив, то він, ймовірно, переживе інші оптимізації, оскільки люди не так помиляються. Це сказано, -fpredictive-commoningє лише в тому -O3, що дозволяє включити помилки у вашому коді, спричинені неправильними припущеннями про одночасність. Чим менше помиляється ваш код, тим менш небезпечною є оптимізація ;-)
Стів Джессоп

6
@PlasmaHH, я не думаю, що "суворіший" є хорошим описом -Ofast, він вимикає IEEE-сумісне поводження з NaN, наприклад
Джонатан Уейклі

Відповіді:


223

У перші дні gcc (2,8 і т.д.) і в часи egcs, і redhat 2,96 -O3 іноді був досить баггі. Але це вже понад десятиліття тому, і -O3 не сильно відрізняється від інших рівнів оптимізації (у баггісті).

Однак, як правило, виявляються випадки, коли люди покладаються на не визначену поведінку, через більш чітке поводження з правилами, а особливо кутовими випадками мови.

Як особиста примітка, я вже багато років працюю над виробничим програмним забезпеченням у фінансовому секторі з -O3 і досі не стикався з помилкою, якої б там не було, якби я використовував -O2.

За популярним попитом, ось додаток:

-O3 і особливо додаткові прапори, такі як -funroll-петлі (не ввімкнено -O3), іноді можуть призвести до генерування більше машинного коду. За певних обставин (наприклад, на процесорі з виключно невеликим кешем інструкцій L1) це може спричинити уповільнення через весь код, наприклад, якийсь внутрішній цикл тепер не вкладається в L1I. Як правило, gcc намагається не генерувати так багато коду, але оскільки він зазвичай оптимізує загальний випадок, це може статися. Варіанти, особливо схильні до цього (наприклад, розмотування циклу), як правило, не включаються до -O3 і відповідно позначаються на сторінці сторінки. Як таке, зазвичай, добре використовувати -O3 для генерування швидкого коду, і лише повернутися до -O2 або -Os (що намагається оптимізувати для розміру коду), коли це доречно (наприклад, коли профайлер вказує, що L1I не вистачає).

Якщо ви хочете вкрай оптимізувати, ви можете налаштувати в gcc через --param витрати, пов'язані з певними оптимізаціями. Додатково зауважте, що тепер gcc має можливість розміщувати атрибути у функціях, які керують налаштуваннями оптимізації саме для цих функцій, тож коли ви виявите, що у вас є проблема з -O3 в одній функції (або ви хочете спробувати спеціальні прапори саме для цієї функції), вам не потрібно складати весь файл або навіть весь проект з O2.

Ото, мабуть, слід бути обережним при використанні -Ofast, де зазначено:

-Ofast дозволяє всі -O3 оптимізації. Він також дозволяє оптимізувати, які не відповідають усім стандартам, що відповідають стандартам.

що змушує мене зробити висновок, що -O3 повинен повністю відповідати стандартам.


2
Я просто використовую щось подібне до навпаки. Я завжди використовую -Os або -O2 (іноді O2 генерує менший виконуваний файл) .. Після профілювання я використовую O3 у частинах коду, які займають більше часу на виконання, і лише вони можуть дати до 20% більше швидкості.
CoffeDeveloper

3
Я роблю це для швидкості. O3 у більшості разів робить все повільніше. Не знаю точно, чому я підозрюю, що це забруднює кеш інструкцій.
CoffeDeveloper

4
@DarioOO Мені здається, що благаюче "розшифрування коду" - це популярна річ, але я майже ніколи не бачу, щоб це було підкріплено тестами. Це багато що залежить від архітектури, але щоразу, коли я бачу опубліковані орієнтири (наприклад, phoronix.com/… ), це показує, що O3 у більшості випадків швидше. Я бачив профілювання та ретельний аналіз, необхідний для підтвердження того, що роздуття коду насправді є проблемою, і це трапляється лише для людей, які сприймають шаблони вкрай.
Нір Фрідман

1
@NirFriedman: Як правило, виникає проблема, коли модель вбудованої вартості компілятора має помилки або коли ви оптимізуєте для зовсім іншої цілі, ніж ви працюєте. Цікаво це стосується всіх рівнів оптимізації ...
PlasmaHH

1
@PlasmaHH: проблему використання cmov було б важко виправити для загальної справи. Зазвичай ви не просто сортували свої дані, тому коли gcc намагається вирішити, передбачувана філія чи ні, статичний аналіз, який шукає дзвінки до std::sortфункцій, навряд чи допоможе. Використання чогось на зразок stackoverflow.com/questions/109710/… допомогло б, або, можливо, напишіть джерело, щоб скористатись відсортованістю: скануйте, поки не побачите> = 128, а потім починайте підбивати підсумки. Що стосується роздутого коду, так, я маю намір обійтись, щоб повідомити про це. : P
Пітер Кордес

42

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

Запропонований шаблон використання - -O3це після профілювання вашої програми вручну, застосувавши її до невеликої жменьки файлів, що містять критичні внутрішні петлі, які фактично виграють від цих агресивних компромісів для швидкості. Новіші версії GCC мають керований профілем режим оптимізації, який може (IIUC) вибірково застосовувати -O3оптимізацію до гарячих функцій - ефективно автоматизуючи цей процес.


10
"майже завжди"? Зробіть це "50-50", і ми матимемо справу ;-).
Заєць без клопів

12

-O3 варіант включає в себе більш дорогі оптимізації, такі як функція вбудовування, на додаток до всіх оптимізацій нижчих рівнів '-O2' та '-O1'. Рівень оптимізації '-O3' може збільшити швидкість отриманого виконуваного файлу, але також може збільшити його розмір. За деяких обставин, коли ці оптимізації не сприятливі, ця опція може насправді зробити програму повільнішою.


3
Я розумію, що деякі "явні оптимізації" можуть зробити програму повільнішою, але чи є у вас джерело, яке стверджує, що GCC -O3 зробила програму повільніше?
Mooing Duck

1
@MooingDuck: Хоча я не можу навести джерело, я пам’ятаю, що натрапив на такий випадок із деякими старими процесорами AMD, які мали зовсім невеликий кеш L1I (~ 10k інструкцій). Я впевнений, що в Google є більше зацікавлених, але особливо такі варіанти, як розкручування циклу, не є частиною O3, а ті, що збільшують розміри, значно збільшують. -Ось це той, коли ви хочете зробити виконуваний файл найменшим. Навіть -O2 може збільшити розмір коду. Гарний інструмент для гри з результатами різних рівнів оптимізації - це gcc explorer.
ПлазмаHH

@PlasmaHH: Насправді, крихітний розмір кешу - це те, що компілятор міг би накрутити, добре. Це справді хороший приклад. Будь ласка, вкажіть це у відповідь.
Mooing Duck

1
@PlasmaHH Pentium III мав кеш-код 16КБ. AM6 K6 і вище фактично мав кеш інструкцій 32 КБ. P4 розпочався вартістю близько 96 КБ. Core I7 насправді має кеш-код кеш-пам'яті L1 32 Кб. Декодери інструкцій на сьогодні є сильними, тому ваш L3 досить хороший, щоб повернутися на майже будь-який цикл.
doug65536

1
Ви побачите величезне збільшення продуктивності кожного разу, коли з’явиться функція, що викликається в циклі, і вона може зробити значне загальне усунення підвыражения і підняття непотрібного перерахунку з функції до циклу.
doug65536

8

Так, O3 - баггір. Я розробник компілятора і виявив явні і очевидні помилки gcc, спричинені O3, що генерує помилкові інструкції щодо збирання SIMD під час створення власного програмного забезпечення. З того, що я бачив, більшість виробничих програм поставляється з O2, а це означає, що O3 буде отримувати менше уваги тестування wrt та виправлення помилок.

Подумайте про це так: O3 додає більше перетворень поверх O2, що додає більше перетворень поверх O1. Статистично кажучи, більше перетворень означає більше помилок. Це справедливо для будь-якого компілятора.


3

Нещодавно у мене виникла проблема використання оптимізації за допомогою g++. Проблема була пов'язана з карткою 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, минаючи всі попередні записи.

На закінчення: будьте точні та уважні з оптимізацією.


38
Але справа в тому, що у вашій програмі просто не визначена поведінка; оптимізатор нічого поганого не зробив. Зокрема, потрібно оголосити pciMemoryяк volatile.
Конрад Рудольф

11
Насправді це не UB, але компілятор має право випустити всі, крім останнього, pciMemoryтому що всі інші записи, мабуть, не мають ефекту. Для оптимізатора це дивовижно, оскільки він може видалити багато непотрібних та трудомістких інструкцій.
Конрад Рудольф

4
Я знайшов це в стандарті (після 10+ років)) - мінлива декларація може бути використана для опису об'єкта, відповідного порту вводу / виводу, відображеному на пам'ять, або об'єкту, до якого звертається функція асинхронного переривання. Дії на так оголошені об'єкти не повинні «оптимізуватися» впровадженням або упорядкованими, за винятком випадків, передбачених правилами оцінки виразів.
borisbn

2
@borisbn Дещо поза темою, але як ви знаєте, що ваш пристрій прийняв команду перед відправленням нової команди?
user877329

3
@ user877329 Я бачив це за поведінкою пристрою, але це був чудовий квест
borisbn
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.