Подтвердить что ты не робот

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

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

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

4b9b3361

Ответ 1

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

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

Ответ 2

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

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

{-# OPTIONS -Wall #-} 

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

Ответ 3

Я удивлен, что никто не упомянул о вездесущем ответе на все проблемы производительности 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

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

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

Ответ 4

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

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

Итак, x1 зависит от x2, который зависит от x3, который зависит от x1. BAD!

Опрыскивать функции трассировки в определениях 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 и что ее второй аргумент (но не первый) имеет круговую зависимость.

Счастливая отладка!

Ответ 5

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

Ответ 6

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

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

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