Почему бы не всегда использовать оптимизацию компилятора?

Один из вопросов, которые я задал некоторое время назад, имел поведение undefined, поэтому оптимизация компилятора фактически вызывала разрыв программы.

Но если в коде отсутствует поведение undefined, тогда есть ли причина не использовать оптимизацию компилятора? Я понимаю, что иногда для целей отладки, возможно, не нужен оптимизированный код (пожалуйста, поправьте меня, если я ошибаюсь). Кроме этого, по производственному коду, почему бы не всегда использовать оптимизацию компилятора?

Кроме того, существует ли когда-либо причина использовать, скажем, -O вместо -O2 или -O3?

Ответ 1

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

Как правило, когда я достигаю такого состояния, мне нравится делать комбинацию:

  • debug build (без оптимизации) и выполните код
  • развернутые диагностические инструкции для stderr, поэтому я могу легко отслеживать путь выполнения

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

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

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

Ответ 2

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

  • Оптимизации почти всегда будут переупорядочивать и/или удалять код. Это уменьшит эффективность отладчиков, поскольку между исходным кодом и сгенерированным кодом больше не будет соответствия 1 к 1. Части стека могут отсутствовать, а пошаговые инструкции могут закончиться пропуском над частями кода противоречивыми способами.
  • Оптимизация обычно дорога для выполнения, поэтому ваш код займет больше времени, чтобы скомпилировать с включенными оптимизациями, чем в противном случае. Трудно делать что-то полезное, пока ваш код компилируется, поэтому явно более короткие сроки компиляции - это хорошо.

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

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

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

Ответ 3

3 причины

  • Смущает отладчик, иногда
  • Это несовместимо с некоторыми шаблонами кода
  • Не стоит: медленный или багги, или занимает слишком много памяти, или создает слишком большой код.

В случае 2 представьте себе некоторый код ОС, который намеренно меняет типы указателей. Оптимизатор может предположить, что объекты неправильного типа не могут ссылаться и генерировать код, который псевдонимы изменяет значения памяти в регистрах и получает ответ "неправильный" 1.

Случай 3 интересен. Иногда оптимизаторы делают код меньше, но иногда они делают его больше. Большинство программ не являются наименьшими бит-привязанными к ЦП и даже теми, которые есть, только 10% или менее кода на самом деле являются вычислительно-интенсивными. Если у оптимизатора есть недостатки, то это всего лишь выигрыш менее 10% от программы.

Если сгенерированный код больше, то он будет менее удобным для кэширования. Это может быть полезно для библиотеки матричной алгебры с алгоритмами O (n 3) в крошечных маленьких циклах. Но для чего-то с более типичной временной сложностью переполнение кэша может сделать программу медленнее. Оптимизаторы могут быть настроены для всего этого, как правило, но если программа является веб-приложением, скажем, она, безусловно, была бы более дружественной для разработчиков, если бы компилятор просто выполнял универсальные вещи и позволял разработчику просто не открывать fancy-tricks Пандора.


1. Такие программы обычно не соответствуют стандарту, поэтому оптимизатор технически "правилен", но все еще не выполняет то, что планировал разработчик.

Ответ 4

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

MS публикует множество исправлений для ошибок оптимизации в своем компиляторе MSVC x86. К счастью, я никогда не сталкивался с этим в реальной жизни. Но это было не так с другими компиляторами. SH4-компилятор в MS Embedded Visual С++ был очень неудачным.

Ответ 5

Simple. Ошибки оптимизации компилятора.

Ответ 6

Просто случилось со мной. Код, созданный swig для взаимодействия Java, является правильным, но не будет работать с -O2 на gcc.

Ответ 7

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

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

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

Ответ 8

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

Использование clang (потому что в gcc даже без флага оптимизации, делает некоторые iptimizations и результат поврежден):

Файл: a.cpp

#include <stdio.h>

int puts(const char *str) {
    fputs("Hello, world!\n", stdout);
    return 1;
}

int main() {
    printf("Goodbye!\n");
    return 0;
}

Без флага OO:

> clang --outoutOptimization a.cpp;./withoutOptimization

> До свидания!

С флагом -Ox:

> clang - выход с O1-O1 a.cpp;./withO1

Привет, мир!

Ответ 9

Одним из примеров является булевая оценка короткого замыкания. Что-то вроде:

if (someFunc() && otherFunc()) {
  ...
}

Компилятор "умный" может понять, что someFunc по какой-то причине всегда возвращает false, делая весь оператор оценивается как false и решает не вызывать otherFunc для экономии времени процессора. Но если otherFunc содержит код, который непосредственно влияет на выполнение программы (возможно, он сбрасывает глобальный флаг или что-то в этом роде), теперь он не будет выполнять этот шаг, и вы введете неизвестное состояние.