32

これは少しあいまいかもしれませんが、私はしばらくの間それを疑問に思っていました。私の知る限り!、値が作成される前に、データコンストラクターのパラメーターが評価されていることを確認できます。

data Foo = Bar !Int !Float

怠惰は素晴らしいことだとよく思います。!さて、ソースを調べると、 -lessバリアントよりも厳密なフィールドが頻繁に表示されます。

これの利点は何ですか、そしてなぜ私はそれをそのまま怠惰にすべきではないのですか?

4

4 に答える 4

40

大規模な計算を Int および Float フィールドに格納していない限り、サンクに蓄積された多くの些細な計算から、かなりのオーバーヘッドが蓄積される可能性があります。たとえば、データ型の遅延 Float フィールドに 1 を繰り返し追加すると、実際にフィールドを強制して計算するまで、ますます多くのメモリが消費されます。

多くの場合、コストのかかる計算をフィールドに格納する必要があります。しかし、事前にそのようなことをしないことがわかっている場合は、フィールドを厳密にマークして、必要seqな効率を得るために手動で追加する必要をなくすことができます。

追加のボーナスとして、フラグを指定すると、-funbox-strict-fieldsGHC はデータ型の厳密なフィールド1をデータ型自体に直接アンパックします。これは、それらが常に評価されることを知っているため可能であり、したがってサンクを割り当てる必要はありません。この場合、Bar 値は、データを含むサンクへの 2 つのポインタを含むのではなく、メモリ内の Bar 値の内部に直接 Int と Float を構成するマシン ワードを含みます。

怠惰は非常に便利なことですが、特に常に見られる (したがって強制される) 小さなフィールドや、頻繁に変更されるが非常に高価な計算では決して行われない小さなフィールドの場合は、単に邪魔になり、計算を妨げることがあります。厳密なフィールドは、データ型のすべての使用を変更することなく、これらの問題を解決するのに役立ちます。

遅延フィールドよりも一般的かどうかは、読んでいるコードの種類によって異なります。たとえば、関数ツリー構造が厳密なフィールドを広範囲に使用することはほとんどありません。

中置操作用のコンストラクターを持つ AST があるとします。

data Exp = Infix Op Exp Exp
         | ...

data Op = Add | Subtract | Multiply | Divide

Expそのようなポリシーを適用すると、最上位ノードを見るたびに AST 全体が評価されることになるため、フィールドを厳密にすることは望ましくありません。ただし、Op後日に延期したい高価な計算がフィールドに含まれることは決してなく、本当に深くネストされた解析ツリーがある場合、中置演算子ごとのサンクのオーバーヘッドが高くなる可能性があります。したがって、中置コンストラクターの場合、Opフィールドを正格にし、2 つのExpフィールドを遅延させておきます。

1単一コンストラクター型のみをアンパックできます。

于 2011-12-20T14:30:54.643 に答える
9

他の回答で提供される情報に加えて、次の点に注意してください。

私の知る限り!、値が構築される前にデータコンストラクターのパラメーターが評価されていることを確認できます

パラメーターが評価される深さを見るのは興味深いことです。これはWHNFと同様にseq評価$!されます。

与えられたデータ型

data Foo = IntFoo !Int | FooFoo !Foo | BarFoo !Bar
data Bar = IntBar Int

表現

let x' = IntFoo $ 1 + 2 + 3
in  x'

WHNF に評価されると、値が生成されますIntFoo 6(== 完全に評価される、== NF)。
さらにこんな表現

let x' = FooFoo $ IntFoo $ 1 + 2 + 3
in  x'

WHNF に評価されると、値が生成されますFooFoo (IntFoo 6)(== 完全に評価される、== NF)。
しかし、この表現は

let x' = BarFoo $ IntBar $ 1 + 2 + 3
in  x'

WHNF に評価されると値が生成されますBarFoo (IntBar (1 + 2 + 3))(!= 完全に評価され、!= NF)。

要点:のデータ コンストラクターに厳密なパラメーター自体が含まれていない場合、パラメーターの厳密性が!Bar必ずしも役立つとは限りません。Bar

于 2015-06-06T12:21:02.157 に答える
4

怠惰に関連するオーバーヘッドがあります。コンパイラは、結果が必要になるまで計算を格納するために、値のサンクを作成する必要があります。遅かれ早かれ常に結果が必要になることがわかっている場合は、結果の評価を強制することは理にかなっています。

于 2011-12-20T14:32:00.940 に答える
4

怠惰には代償が伴います。

コストは 2 倍です。

  1. 操作をすぐに実行するよりも、サンク (つまり、最終的に計算されるときに計算する必要があるものの記述) をセットアップするのに時間がかかる場合があります。
  2. 他のサンクなどへの非厳密な引数として行く他のサンクへの非厳密な引数として行く未評価のサンクは、ますます多くのメモリを使用します。残念なことに、これらの tunk は、もうアクセスできないメモリへの参照も保持している可能性があります。つまり、thunk のみが評価されるときに解放される可能性があるメモリであり、ガベージ コレクタがその作業を実行できなくなります。例としては、ツリー内の特定の値を更新することになっているサンクがあります。この値が 100MB 相当の他の値に保持されているとします。古いツリーへの参照がなくなった場合、サンクが評価されない限り、このメモリは無駄になります。
于 2011-12-20T22:01:25.807 に答える