Почему в программах на C / C ++ оптимизация часто отключена в режиме отладки?

StackOverflow https://stackoverflow.com/questions/69250

  •  09-06-2019
  •  | 
  •  

Вопрос

В большинстве сред C или C ++ существует режим "отладки" и режим компиляции "выпуска".
Рассматривая разницу между ними, вы обнаруживаете, что режим отладки добавляет символы отладки (часто параметр -g во многих компиляторах), но он также отключает большинство оптимизаций.
В режиме "release" у вас обычно включены всевозможные оптимизации.
В чем разница?

Это было полезно?

Решение

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


void foo() {
1:  int i;
2:  for(i = 0; i < 2; )
3:    i++;
4:  return;

В этом примере, без оптимизации, вы могли бы выполнить один шаг по коду и нажать на строки 1, 2, 3, 2, 3, 2, 4

При включенной оптимизации вы можете получить путь выполнения, который выглядит следующим образом:2, 3, 3, 4 или даже просто 4!(В конце концов, функция ничего не делает ...)

Суть в том, что отладка кода с включенной оптимизацией может быть настоящей пыткой!Особенно, если у вас большие функции.

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

Хотя оптимизированный и неоптимизированный код должен быть "функционально" эквивалентен, при определенных обстоятельствах поведение изменится.
Вот упрощенный пример:

    int* ptr = 0xdeadbeef;  // some address to memory-mapped I/O device
    *ptr = 0;   // setup hardware device
    while(*ptr == 1) {    // loop until hardware device is done
       // do something
    }

При отключенной оптимизации это просто, и вы вроде как знаете, чего ожидать.Однако, если вы включите оптимизацию, может произойти несколько вещей:

  • Компилятор может оптимизировать блок while (мы инициализируем значение 0, оно никогда не будет равно 1).
  • Вместо доступа к памяти доступ к указателю может быть перемещен в регистр-> Обновление ввода-вывода отсутствует
  • доступ к памяти может быть кэширован (не обязательно связанный с оптимизацией компилятора).

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

Другие советы

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

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

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

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

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

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

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

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

Ожидается, что отладочная версия будет - debugged!Установка точек останова, пошаговое выполнение при просмотре переменных, трассировка стека и все остальное, что вы делаете в отладчике (IDE или ином), имеет смысл, если каждая строка непустого исходного кода без комментариев соответствует некоторой инструкции машинного кода.

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

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

В подразделении Windows в Microsoft все двоичные файлы выпуска создаются с использованием символов отладки и полной оптимизации.Символы хранятся в отдельных PDB-файлах и не влияют на производительность кода.Они не поставляются вместе с продуктом, но большинство из них доступны на сайте Сервер символов Microsoft.

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

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

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

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

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

По крайней мере, в Linux (и нет причин, по которым Windows должна отличаться) отладочная информация упаковывается в отдельный раздел двоичного файла и не загружается при обычном выполнении.Они могут быть разделены на другой файл, который будет использоваться для отладки.Кроме того, в некоторых компиляторах (включая Gcc, я полагаю, также с компилятором Microsoft C) отладочная информация и оптимизация могут быть включены одновременно.Если нет, то очевидно, что код будет работать медленнее.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top