108

m2が次のようになっていないのに、なぜm1が明らかにメモ化されているのか理解できません。

m1      = ((filter odd [1..]) !!)

m2 n    = ((filter odd [1..]) !! n)

m1 10000000は最初の呼び出しで約1.5秒かかり、後続の呼び出しではその一部(おそらくリストをキャッシュします)ですが、m2 10000000は常に同じ時間かかります(呼び出しごとにリストを再構築します)。何が起こっているのか分かりますか?GHCが機能をメモ化するかどうか、いつメモ化するかについての目安はありますか?ありがとう。

4

4 に答える 4

115

GHC は関数をメモ化しません。

ただし、コード内の特定の式は、周囲のラムダ式が入力されるたびに最大 1 回、最上位にある場合は最大 1 回計算されます。例のようにシンタックス シュガーを使用する場合、ラムダ式がどこにあるかを判断するのは少し難しい場合があるため、これらを同等の desugared 構文に変換しましょう。

m1' = (!!) (filter odd [1..])              -- NB: See below!
m2' = \n -> (!!) (filter odd [1..]) n

(注: Haskell 98 のレポートでは、実際には のような左演算子セクションを(a %)と同等と記述しています\b -> (%) a bが、GHC はそれを に脱糖し(%) aます。これらは で区別できるため、技術的には異なりますseq。これについて GHC Trac チケットを提出した可能性があると思います。)

これを考えるとm1'、 では式filter odd [1..]がどのラムダ式にも含まれていないことがわかります。したがって、プログラムの実行ごとに 1 回だけ計算されますが、m2'ではfilter odd [1..]ラムダ式が入力されるたびに計算されます。つまり、の呼び出しごとにm2'。それはあなたが見ているタイミングの違いを説明しています。


実際、特定の最適化オプションを備えた GHC のいくつかのバージョンは、上記の説明が示すよりも多くの値を共有します。これは、状況によっては問題になる可能性があります。たとえば、関数を考えてみましょう

f = \x -> let y = [1..30000000] in foldl' (+) 0 (y ++ [x])

yGHCは関数に依存していないことに気づきx、関数を

f = let y = [1..30000000] in \x -> foldl' (+) 0 (y ++ [x])

この場合、新しいバージョンは が格納されているメモリから約 1 GB を読み取る必要があるため、効率が大幅に低下しますがy、元のバージョンは一定のスペースで実行され、プロセッサのキャッシュに収まります。実際、GHC 6.12.1 では、この関数は、最適化なしfでコンパイルした場合に、 を使用してコンパイルした場合よりもほぼ 2 倍速くなります。-O2

于 2010-10-16T22:30:26.797 に答える
30

m1 は Constant Applicative Form であるため 1 回だけ計算されますが、m2 は CAF ではないため、評価ごとに計算されます。

CAF に関する GHC wiki を参照してください: http://www.haskell.org/haskellwiki/Constant_applicative_form

于 2010-10-16T22:43:09.393 に答える
15

2 つの形式には決定的な違いがあります。m2 には引数が明示的に与えられているため、m1 には単型性制限が適用されますが、m2 には適用されません。したがって、m2 のタイプは一般的ですが、m1 のタイプは特定的です。割り当てられるタイプは次のとおりです。

m1 :: Int -> Integer
m2 :: (Integral a) => Int -> a

ほとんどの Haskell コンパイラとインタプリタ (私が実際に知っているものはすべて) は、ポリモーフィック構造をメモ化しないため、m2 の内部リストは呼び出されるたびに再作成されますが、m1 はそうではありません。

于 2010-10-18T13:55:09.853 に答える