Я пробовал: valgrind, _GLIBCXX_DEBUG, -fno-strict-aliasing;как мне отладить эту ошибку? - PullRequest
4 голосов
/ 21 июля 2011

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

Некоторый фон. Я работаю над программным проектом, который включает добавление расширений C ++ в Python 2.7.1 с использованием Boost 1.45, поэтому весь мой код выполняется через интерпретатор Python. Недавно я внес изменения в код, который нарушил один из наших регрессионных тестов. Этот регрессионный тест, вероятно, слишком чувствителен к численным колебаниям (например, на разных машинах), поэтому я должен это исправить. Однако, поскольку эта регрессия нарушается на том же компьютере / компиляторе, который дал исходные результаты регрессии, я проследил разницу в результатах с этим фрагментом числового кода (который достоверно не связан с измененным кодом):

c[3] = 0.25 * (-3 * df[i-1] - 23 * df[i] - 13 * df[i+1] - df[i+2]
               - 12 * f[i-1] - 12 * f[i] + 20 * f[i+1] + 4 * f[i+2]);
printf("%2li %23a : %23a %23a %23a %23a : %23a %23a %23a %23a\n",i,
       c[3],
       df[i-1],df[i],df[i+1],df[i+2],f[i-1],f[i],f[i+1],f[i+2]);

, который строит некоторые числовые таблицы. Обратите внимание, что:

  • % отпечатков обеспечивает точное представление ascii
  • Левая часть (lhs) - c [3], а rhs - остальные 8 значений.
  • Вывод ниже был для значений i, которые были далеки от границ f, df
  • этот код существует в цикле над i, который сам вложил несколько слоев (поэтому я не могу представить отдельный случай, чтобы воспроизвести это).

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

diff new.out old.out 
655,656c655,656
<  6  -0x1.7c2a5a75fc046p-10 :                  0x0p+0                  0x0p+0                  0x0p+0   -0x1.75eee7aa9b8ddp-7 :    0x1.304ec13281eccp-4    0x1.304ec13281eccp-4    0x1.304ec13281eccp-4    0x1.1eaea08b55205p-4
<  7   -0x1.a18f0b3a3eb8p-10 :                  0x0p+0                  0x0p+0   -0x1.75eee7aa9b8ddp-7   -0x1.a4acc49fef001p-6 :    0x1.304ec13281eccp-4    0x1.304ec13281eccp-4    0x1.1eaea08b55205p-4    0x1.9f6a9bc4559cdp-5
---
>  6  -0x1.7c2a5a75fc006p-10 :                  0x0p+0                  0x0p+0                  0x0p+0   -0x1.75eee7aa9b8ddp-7 :    0x1.304ec13281eccp-4    0x1.304ec13281eccp-4    0x1.304ec13281eccp-4    0x1.1eaea08b55205p-4
>  7  -0x1.a18f0b3a3ec5cp-10 :                  0x0p+0                  0x0p+0   -0x1.75eee7aa9b8ddp-7   -0x1.a4acc49fef001p-6 :    0x1.304ec13281eccp-4    0x1.304ec13281eccp-4    0x1.1eaea08b55205p-4    0x1.9f6a9bc4559cdp-5
<more output truncated>

Вы можете видеть, что значение в c [3] слегка отличается, хотя ни одно из значений rhs не отличается. Таким образом, некоторые идентичные данные приводят к разным результатам. Я попытался упростить выражение rhs, но любое изменение, которое я делаю, устраняет разницу. Если я напечатаю & c [3], то разница исчезнет. Если я работаю на двух разных машинах (linux, osx), к которым у меня есть доступ, разницы нет. Вот что я уже пробовал:

  • valgrind (сообщалось о многочисленных проблемах в python, но ничего в моем коде и ничего серьезного)
  • -D_GLIBCXX_DEBUG -D_GLIBCXX_DEBUG_ASSERT -D_GLIBCXX_DEBUG_PEDASSERT -D_GLIBCXX_DEBUG_VERIFY (но ничего не утверждается)
  • -fno-strict-aliasing (но я получаю псевдонимы предупреждений компиляции из кода повышения)

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

Могу ли я что-нибудь сделать, чтобы изолировать проблему дальше? Для дальнейшего использования, есть ли способ быстрее проанализировать или понять проблему такого рода? Например, учитывая моё описание изменения lhs, хотя rhs нет, что бы вы сделали вывод?

EDIT: Проблема была полностью из-за -ffast-math.

1 Ответ

4 голосов
/ 21 июля 2011

Вы можете изменить тип данных с плавающей точкой вашей программы. Если вы используете float, вы можете переключиться на double; если c, f, df - double, вы можете переключиться на long double (80 бит на intel; 128 на sparc). В 4.5.2 вы даже можете попробовать использовать _float128 (128-битный) программно-имитируемый тип.

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

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

Также попробуйте скомпилировать проект с флагом -mfpmath=sse -msse2, потому что для x87 (по умолчанию fpmath для более старых gcc) используется http://gcc.gnu.org/wiki/x87note

по умолчанию x87 арифметика неверна 64/32 бит IEEE

PS: вам не следует использовать -ffast-math -подобные опции, если вы заинтересованы в стабильных числовых результатах: http://gcc.gnu.org/onlinedocs/gcc-4.1.1/gcc/Optimize-Options.html

-ffast-math Устанавливает -fno-math-errno, -funsafe-math-optimizations, -fno-trapping-math, -ffinite-math-only, -fno-rounding-math, -fno-signaling-nans и fcx-limited-range.

Эта опция заставляет макрос препроцессора FAST_MATH быть определен.

Эта опция никогда не должна включаться никаким параметром -O, поскольку она может привести к неверному выводу для программ , которые зависят от точной реализации правил / спецификаций IEEE или ISO для математические функции.

Эта часть быстрой математики может изменить результаты

-funsafe-math-optimizations Разрешить оптимизацию для арифметики с плавающей точкой , которая (a) предполагает, что аргументы и результаты являются действительными, и (b) может нарушать стандарты IEEE или ANSI . При использовании во время компоновки он может включать библиотеки или файлы запуска, которые изменяют стандартное управляющее слово FPU или другие подобные оптимизации.

Эта часть будет скрывать ловушки и NaN-подобные ошибки от пользователя (иногда пользователь хочет получить все ловушки именно для отладки своего кода)

-fno-trapping-math Скомпилируйте код , предполагая, что операции с плавающей запятой не могут генерировать видимых пользователю traps . Эти ловушки включают деление на ноль, переполнение, недополнение, неточный результат и недопустимая операция. Эта опция подразумевает -fno-signaling-nans. Установка этой опции может позволить более быстрый код, если, например, используется «безостановочная» арифметика IEEE.

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

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

...