Отладка бесконечных циклов в программах на Haskell с помощью GHCi

Впервые я столкнулся с бесконечным циклом в программе на Haskell, которую я пишу. Я сузил его до довольно специфического раздела кода, но я не могу точно определить, где именно у меня есть неразрывное рекурсивное определение. Я смутно знаком с:trace и:history в GHCi, но проблема в том, что некоторые ветви моего кода содержат довольно много рекурсивных модификаций Data.Map.Map в том смысле, что карта x получается adjustчто-то на карте x' на основе значений в другой карте в зависимости от x', Специфика здесь не имеет значения, но, как вы, вероятно, можете сказать, если это происходит переплетенным рекурсивным способом, моя история вызовов полностью увязает во всех различных сравнениях, связанных с картой. lookups, adjustments и insertионов.

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

6 ответов

Решение

Убедитесь, что вы в полной мере использовали отладчик GHCi, включая параметр -fbreak-on-exception (полезно, если вы получаете <<loop>>, да?) и убедитесь, что вы воспользовались советом Стивена об использовании предупреждений GHC.

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

Я удивлен, что никто не упомянул вездесущий ответ, который получают все проблемы производительности Haskell (бесконечное время выполнения - довольно экстремальный пример "проблемы производительности"): профилирование!

Я просто смог быстро идентифицировать бесконечный цикл, используя профилирование. Для полноты, скомпилируйте с -prof -fprof-auto, затем запустите программу на достаточное время, чтобы нарушающая функция была видна в статистике профилирования. Например, я ожидал, что моя программа завершится менее чем за 1 секунду, поэтому я позволил профилировщику работать около 30 секунд, а затем убил свою программу с помощью Ctrl+C. (Примечание. Профилирование сохраняет инкрементные результаты, поэтому вы все равно можете получить значимые данные, даже если вы убьете программу до ее запуска. EDIT: за исключением случаев, когда этого не происходит.)

В файле.prof я обнаружил следующий блок:

                                                 individual      inherited
COST CENTRE         MODULE     no.    entries   %time  %alloc   %time %alloc
...
primroot.\          Zq         764          3    10.3    13.8    99.5  100.0
 primroot.isGen     Zq         1080   50116042    5.3     6.9    89.2   86.2
  primroot.isGen.\  Zq         1087   50116042   43.4    51.7    83.8   79.3
   fromInteger      ZqBasic    1088          0   40.4    27.6    40.4   27.6

Таким образом, 50 миллионов записей primroot.isGenи следующая наиболее часто вызываемая функция имела только 1024 вызова. Кроме того, 99,5% времени выполнения было потрачено на вычисления primroot, что кажется весьма подозрительным. Я проверил эту функцию и быстро нашел ошибку, в моем случае простая опечатка: (`div` foo) вместо (div foo),

Я думаю, что стоит отметить, что предупреждения GHC не поймали бы эту проблему, ни -fbreak-on-exceptions, База кода огромна; Попытка отследить проблему, вставив отладочные операторы (любого рода), ни к чему не привела. Мне также не удалось использовать отладчик GHCi, потому что история по существу не существовала, и HPC не обнаружил ничего полезного.

Как говорит ShiDoiSi, решение "на глаз" часто является наиболее успешным способом.

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

{-# OPTIONS -Wall #-} 

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

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

...
x1 = f1 x2 y
x2 = f2 z x3
x3 = f3 y x1
...

Таким образом, x1 зависит от x2, который зависит от x3, который зависит от x1. ПЛОХОЙ!

Посыпать следовые функции в определениях f1, f2, f3. Что-то вроде:

f1 x y | trace ("f1: ") False = undefined
f1 x y = ... -- definition of f1

f2 x y | trace ("f2: ") False = undefined
f2 x y = ... -- definition of f2

-- same for f3

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

f3:
f2:
f1: 
<<loop>>

Затем начните показывать некоторые переменные в функциях трассировки. Например, если вы измените след f2 на

f2 x y | trace ("f2: x: " ++ show x) False = undefined

тогда вывод будет выглядеть примерно так:

f3:
f2: x: x_value
f1: 
<<loop>>

Но если вы затем измените след f2 на

f2 x y | trace ("f2: x: " show x ++ " y: " ++ show y) False = undefined

Тогда вывод будет

f3:
<<loop>>

потому что второй аргумент f2 не может быть оценен из-за круговой зависимости.

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

Удачной отладки!

Взломав довольно много строк на Haskell, я должен сказать, что мне никогда не удавалось с отладкой. Всегда была тщательная доработка кода и рефакторинг, который в конечном итоге помог мне отследить проблему. Но, конечно, это просто анекдотично.

Разве вы не можете использовать: назад и: вперед, чтобы посетить свою историю / трассировку и выяснить эволюцию ваших карт между вызовами?

Вы должны быть в состоянии определить шаблон, который приводит к рекурсивному циклу.

- Если это слишком сложно, вы, возможно, достигли точки, когда вы пишете какой-то код, слишком умный для отладки (или, может быть, слишком сложный, и вы должны реорганизовать его ^^)-

Другие вопросы по тегам