Почему программы на C / C ++ часто отключают оптимизацию в режиме отладки? - PullRequest
12 голосов
/ 16 сентября 2008

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

Ответы [ 6 ]

28 голосов
/ 16 сентября 2008

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


void foo() {
1:  int i;
2:  for(i = 0; i &lt 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)
  • Вместо доступа к памяти, указатель доступа может быть перенесен в регистр-> Нет обновления ввода / вывода
  • доступ к памяти может быть кэширован (необязательно связан с оптимизацией компилятора)

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

4 голосов
/ 16 сентября 2008

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

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

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

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

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

3 голосов
/ 16 сентября 2008

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

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

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

2 голосов
/ 16 сентября 2008

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

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

1 голос
/ 11 января 2009

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

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

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

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

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

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

1 голос
/ 16 сентября 2008

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...