24

私が書いている Haskell プログラムで、初めて無限ループに遭遇しました。コードの非常に特定のセクションに絞り込みましたが、終了しない再帰定義がある場所を正確に特定できないようです。私は GHCi の :trace と :history に漠然と精通していますが、問題は、値に基づいてマップ内の何かを ing することによってマップが取得されるData.Map.Mapという意味で、私のコードの一部の分岐で a の再帰的な変更がかなり含まれていることです。に応じて別のマップで。ここでは詳細は重要ではありませんが、おそらくおわかりのように、これが絡み合った再帰的な方法で発生した場合、私の呼び出し履歴は、 map 、ments 、およびion に関連するさまざまな比較のすべてで完全に行き詰まります。xadjustx'x'lookupadjustinsert

無限ループを見つけるためのより効率的な方法を推奨できる人はいますか? たとえば、通話履歴を単一のソース ファイルからの通話に制限すると、非常に役立ちます。

4

5 に答える 5

15

すべての 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

したがって、 には 5000 万のエントリがprimroot.isGenあり、2 番目に多く呼び出される関数の呼び出しは 1024 回しかありません。さらに、実行時間の 99.5% がコンピューティングに費やさprimrootれており、非常に疑わしいと思われます。私はその関数をチェックアウトし、私の場合は単純なタイプミスであるエラーをすぐに見つけました:(`div` foo)の代わりに(div foo).

GHC の警告ではこの問題が検出されなかったことは注目に値すると思います-fbreak-on-exceptions。コード ベースは巨大です。(あらゆる種類の) デバッグ ステートメントを挿入して問題を突き止めようとしても、うまくいきませんでした。また、履歴が本質的に存在せず、HPC が有用なものを明らかにしなかったため、GHCi デバッガーの使用にも失敗しました。

于 2014-04-13T18:47:30.123 に答える
11

ShiDoiSi が言うように、「目で」解決することが最も成功する方法です。

同じ関数内で同様の名前の変数 x、x' などにバインドしている場合は、ファイルの先頭で警告を有効にしてみてください。

{-# OPTIONS -Wall #-} 

間違ったものにバインドしていて暴走再帰を行っていることが問題である場合、たとえばシャドウイングの予期しない使用法を示すことで、これを見つけるのに役立つかもしれません。

于 2011-03-17T12:43:00.290 に答える
7

-fbreak-on-exception の設定を含め、GHCi デバッガーを最大限に使用したことを確認してください ( <<loop>>.

これらが失敗した場合 (GHCi デバッガーは実際には「失敗」するべきではありません。データを解釈するだけの問題です) 、ループしている場合に評価されていないブランチと値を視覚的に確認できるように、ループしているケースでHPCを実行してみてください。そうすると、やらなければならないことがおそらく評価されず、マークアップされた HTML に表示されます。

于 2011-03-17T15:55:22.463 に答える
7

無限ループの原因を見つけるための長いデバッグ セッションの最中です。私は非常に近づいています、そしてこれが私を最も助けたものです. ループが次のような原因で発生したとします。

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

したがって、x1 は x2 に依存し、x2 は x1 に依存する x3 に依存します。悪い!

f1、f2、f3 の定義に trace 関数を散りばめます。何かのようなもの:

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 の 2 番目の引数を評価できないためです。

これで、無限ループ内の関数の 1 つが f2 であり、その 2 番目の引数 (最初の引数ではない) に循環依存があることがわかりました。

ハッピーデバッグ!

于 2012-08-30T06:52:00.533 に答える
3

:back と :forward を使用して履歴/トレースにアクセスし、呼び出し間のマップの進化を把握することはできませんか?

再帰ループにつながるパターンを見つけることができるはずです。

--トリッキーすぎる場合は、デバッグするにはあまりにもインテリジェントなコードを記述している可能性があります (または、複雑すぎてリファクタリングする必要があります ^^)--

于 2011-03-17T16:10:10.737 に答える