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

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

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

Ответы [ 5 ]

12 голосов
/ 13 апреля 2014

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

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

В файле .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 не обнаружил ничего полезного.

9 голосов
/ 17 марта 2011

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

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

{-# OPTIONS -Wall #-} 

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

7 голосов
/ 30 августа 2012

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

...
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, а второй аргумент (но не первый) имеет круговую зависимость.

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

7 голосов
/ 17 марта 2011

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

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

3 голосов
/ 17 марта 2011

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

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

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

...