C оптимизация ломает алгоритм - PullRequest
0 голосов
/ 09 апреля 2010

Я программирую алгоритм, который содержит 4 вложенных цикла.Проблема в том, что на каждом уровне указатель обновляется.Внутренний цикл использует только один из указателей.Алгоритм делает сложный подсчет.Когда я включаю оператор отладки, который регистрирует комбинацию индексов и результатов подсчета, я получаю правильный ответ.Когда оператор отладки опущен, счетчик неверен.Программа компилируется с опцией -O3 на gcc.Почему это случилось?

Ответы [ 5 ]

6 голосов
/ 09 апреля 2010

Всегда проверяйте свой код на чем-то вроде valgrind, Purify и т. Д., Прежде чем обвинять оптимизатор. Особенно когда обвиняешь вещи, связанные с указателями.

Нельзя сказать, что оптимизатор не сломан, но, скорее всего, это вы. Я работал над различными компиляторами C ++ и видел свою долю ошибок seg, которые происходят только с оптимизированным кодом. Довольно часто люди делают такие вещи, как забывают посчитать \ 0 при выделении места для строки и т. Д. И просто удача в том месте, на каких страницах вы размещаетесь, когда программа запускается с различными настройками -O.

Кроме того, важные вопросы: вы вообще имеете дело с ограниченными указателями?

1 голос
/ 09 апреля 2010

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

У меня был похожий опыт с одним моим проектом - он отлично работает с -O2, но ломается с -O3. Я использовал setjmp() / longjmp() в своем коде, и мне пришлось сделать половину переменных volatile, чтобы все заработало, поэтому я решил, что -O2 достаточно хорошо.

1 голос
/ 09 апреля 2010

Распечатайте код сборки, сгенерированный компилятором, с оптимизацией.Сравните листинг кода на ассемблере без оптимизации.

Компилятор, возможно, понял, что некоторые переменные могут быть исключены.Они не были использованы в вычислениях.Вы можете попытаться сопоставить остроумие с неиспользуемыми переменными компилятора и факторинга.

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

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

0 голосов
/ 09 апреля 2010

Без кода это сложно, но вот некоторые вещи, которые я видел раньше.

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

Подобное происходит, когда в список аргументов вашего оператора печати включены побочные эффекты.

printf("%i %i\n", x, y = x - z);

Другой тип ошибки может быть:

for( i = 0; i < END; i++) {
     int *a = &i;
     foo(a);
}
if (bar) {
     int * a;
     baz(a);
}

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

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

Вам определенно следует попробовать скомпилировать с предупреждениями, максимальными значениями которых являются (-Wall для gcc). Это часто говорит вам о рискованном коде.

(редактировать) Просто подумал о другом.

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

Первое, если значение может быть изменено обработчиком сигнала или другим потоком. Вы должны сообщить об этом компилятору, чтобы он знал, что любой доступ предполагает, что значение необходимо перезагрузить и / или сохранить. Это делается с помощью ключевого слова volatile.

Второй псевдоним. Это когда вы создаете два разных способа доступа к одной и той же памяти. Компиляторы обычно быстро предполагают, что вы используете псевдонимы с указателями, но не всегда. Кроме того, они являются флагами оптимизации для некоторых из них, которые говорят им, что им не нужно быстро делать такие предположения, а также способами, которыми вы можете обмануть компилятор (сумасшедшие вещи, такие как while (foo != bar) { foo++; } *foo = x;, явно не являющиеся копией bar to foo).

0 голосов
/ 09 апреля 2010

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

Это чистый C или есть какая-то сумасшедшая вещь, как встроенная сборка?

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

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