15

Haskell についてはかなりの理解がありますが、どのような種類のプラグマと最適化をどこで使用する必要があるかについては、常にほとんど確信が持てません。お気に入り

  • SPECIALIZEプラグマをいつ使用するか、どのようなパフォーマンスが得られるかなど。
  • 使用する場所RULES。発砲しない特定のルールについて人々が取っていると聞きましたか? どうやってそれを確認しますか?
  • 関数の引数を厳密にするのはいつで、それが役立つのはいつですか? 引数を厳密にすると、引数が通常の形式に評価されることを理解していますが、すべての関数引数に厳密性を追加しないのはなぜですか? どうやって決めるの?
  • プログラムにスペース リークがあることを確認するにはどうすればよいですか? スペースリークを構成する一般的なパターンは何ですか?
  • あまりにも怠惰で問題があるかどうかを確認するにはどうすればよいですか? ヒープ プロファイリングはいつでも確認できますが、怠惰が害を及ぼす一般的な原因、例、およびパターンを知りたいですか?

特に haskell に特有の高度な最適化 (高レベルと非常に低レベルの両方) について話しているソースはありますか?

4

2 に答える 2

18

SPECIALIZEプラグマをいつ使用するか、どのようなパフォーマンスが得られるかなど。

(型クラス) ポリモーフィック関数がある場合、コンパイラに関数を特化させ、クラスの 1 つまたはいくつかのインスタンスで頻繁に呼び出されることを期待します。

特殊化により、使用されている辞書検索が削除され、多くの場合、さらなる最適化が可能になります。クラス メンバー関数は、多くの場合インライン化でき、厳密性分析の対象となります。どちらも潜在的に大きなパフォーマンスの向上をもたらします。可能な唯一の最適化が辞書ルックアップの排除である場合、一般に利益は大きくありません。

{-# INLINABLE #-}GHC-7 の時点では、関数にプラグマを与える方がおそらくより便利です。これにより、(ほとんど変更されず、正規化と脱糖が実行されます) ソースがインターフェイス ファイルで利用できるようになります。呼び出しサイト。

使用する場所RULES。発砲しない特定のルールについて人々が取っていると聞きましたか? どうやってそれを確認しますか?

-ddump-rule-firingsコマンド ライン オプションを使用して、どのルールが実行されたかを確認できます。これは通常、多数の起動済みルールをダンプするため、独自のルールを少し検索する必要があります。

ルールを使用します

  • 特殊なタイプの関数のより効率的なバージョンがある場合、たとえば

    {-# RULES
    "realToFrac/Float->Double"  realToFrac   = float2Double
      #-}
    
  • 一部の関数をより効率的な特別な引数のバージョンに置き換えることができる場合。

    {-# RULES
    "^2/Int"        forall x. x ^ (2 :: Int) = let u = x in u*u
    "^3/Int"        forall x. x ^ (3 :: Int) = let u = x in u*u*u
    "^4/Int"        forall x. x ^ (4 :: Int) = let u = x in u*u*u*u
    "^5/Int"        forall x. x ^ (5 :: Int) = let u = x in u*u*u*u*u
    "^2/Integer"    forall x. x ^ (2 :: Integer) = let u = x in u*u
    "^3/Integer"    forall x. x ^ (3 :: Integer) = let u = x in u*u*u
    "^4/Integer"    forall x. x ^ (4 :: Integer) = let u = x in u*u*u*u
    "^5/Integer"    forall x. x ^ (5 :: Integer) = let u = x in u*u*u*u*u
      #-}
    
  • 一般的な法則に従って式を書き直すと、最適化したほうがよいコードが生成される場合があります。

    {-# RULES
    "map/map"  forall f g. (map f) . (map g) = map (f . g)
      #-}
    

RULES後者のスタイルでの の広範な使用は、フュージョン フレームワーク (textライブラリなど) で行われます。 のリスト関数ではbase、ルールを使用して異なる種類のフュージョン (foldr/buildフュージョン) が実装されます。

関数の引数を厳密にするのはいつで、それが役立つのはいつですか? 引数を厳密にすると、引数が通常の形式に評価されることを理解していますが、すべての関数引数に厳密性を追加しないのはなぜですか? どうやって決めるの?

引数を strict にすると、通常の形式ではなく、弱いヘッドの通常の形式に評価されることが保証されます。

すべての引数を厳密にするわけではありません。一部の関数は、一部の引数が完全に機能するために非厳密でなければならず、一部の関数はすべての引数が厳密であると効率が低下するためです。

たとえば partition無限リストで機能するには、2 番目の引数が厳密でない必要があります。より一般的には、無限リストで機能するために、使用されるすべての関数foldrが 2 番目の引数で厳密でない必要があります。有限リストでは、2 番目の引数に非正格関数を指定すると、劇的に効率が向上します ( foldr (&&) True (False:replicate (10^9) True))。

とにかく価値のある作業を行う前に引数を評価する必要があることがわかっている場合は、引数を厳密にします。多くの場合、GHC の厳密性アナライザーは単独でそれを行うことができますが、もちろんすべてではありません。

非常に典型的なケースは、ループまたは末尾再帰のアキュムレータです。厳密性を追加すると、途中で巨大なサンクが構築されなくなります。

どこに厳密性を追加するかについての厳密なルールはわかりません。私にとっては経験の問題です.

経験則として、小さなデータ ( など) を評価し続けることは理にかなっていますIntが、例外もあります。

プログラムにスペース リークがあることを確認するにはどうすればよいですか? スペースリークを構成する一般的なパターンは何ですか?

最初のステップは、+RTS -sオプションを使用することです (プログラムが rtsopts を有効にしてリンクされている場合)。これは、全体でどれだけのメモリが使用されたかを示しており、多くの場合、それによってリークがあるかどうかを判断できます。オプションを指定してプログラムを実行すると、より有益な出力が得られ+RTS -hTます。これにより、スペース リークの特定に役立つヒープ プロファイルが生成されます (また、プログラムを有効な rtsopts とリンクする必要があります)。

さらに分析が必要な場合は、プロファイリングを有効にしてプログラムをコンパイルする必要があります (-rtsops -prof -fprof-auto古い GHC では、この-fprof-autoオプションは使用できませんでした。この-prof-auto-allオプションは最も近い対応です)。

次に、さまざまなプロファイリング オプションを指定して実行し、生成されたヒープ プロファイルを確認します。

スペース リークの最も一般的な 2 つの原因は次のとおりです。

  • あまりにも怠惰
  • 厳しすぎる

3 番目はおそらく不要な共有によって占められています。GHC は一般的な部分式の削除をほとんど行いませんが、不要な場所でも長いリストを共有することがあります。

リークの原因を見つけるための厳密なルールはありません。また、ある場所に厳密性を追加したり、別の場所に遅延性を追加したりすることで、リークを修正できる場合もあります。

あまりにも怠惰で問題があるかどうかを確認するにはどうすればよいですか? ヒープ プロファイリングはいつでも確認できますが、怠惰が害を及ぼす一般的な原因、例、およびパターンを知りたいですか?

一般に、遅延は、結果を段階的に構築できる場合に必要とされ、処理が完了する前に結果の一部を配信できない場合には望ましくありません。たとえば、左折畳みや末尾再帰関数などです。

于 2012-09-21T17:27:15.997 に答える
3

プラグマ書き換えルールに関するGHCのドキュメントを読むことをお勧めします。これは、SPECIALIZEとルールに関する多くの質問に対応しているためです。

質問に簡単に対処するには:

  • SPECIALIZEは、コンパイラーに特定のタイプのポリモーフィック関数の特殊なバージョンを作成するように強制するために使用されます。その場合に関数を適用すると、辞書が不要になるという利点があります。欠点は、プログラムのサイズが大きくなることです。特殊化は、「内部ループ」で呼び出される関数にとって特に価値があり、まれにしか呼び出されないトップレベル関数では本質的に役に立ちません。INLINEとの相互作用については、 GHCのドキュメントを参照してください。

  • RULESを使用すると、有効であることがわかっているが、コンパイラーがそれ自体で推測できなかった書き換えルールを指定できます。一般的な例は{-# RULES "mapfusion" forall f g xs. map f (map g xs) = map (f.g) xs #-}、GHCに融合方法を指示するmapです。INLINEに干渉するため、GHCにルールを使用させるのは難しい場合があります。 7.19.3は、競合を回避する方法と、通常は回避する場合でもGHCにルールの使用を強制する方法に触れています。

  • 厳密な引数は、末尾再帰関数のアキュムレータのようなものにとって最も重要です。値は最終的に完全に計算されることを知っています。計算を遅らせるためにクロージャのスタックを構築すると、目的が完全に無効になります。無限リストのように遅延処理する必要のある値に関数を適用する場合は常に、強制的な厳密さを回避する必要があります。一般に、最良のアイデアは、最初は明らかに有用な場合(アキュムレータなど)にのみ厳密性を強制し、その後、プロファイリングで必要であることが示された場合にのみさらに追加することです。

  • 私の経験では、ほとんどの目立たないスペースリークは、非常に大きなデータ構造のレイジーアキュムレータと未評価のレイジー値から発生しましたが、これは作成しているプログラムの種類に固有であると確信しています。ボックス化されていないデータ構造を可能な限り使用すると、多くの問題が修正されます。

  • 怠惰がスペースリークを引き起こす場合を除いて、それを避けるべき主な状況はIOです。リソースを怠惰に処理すると、本質的に、リソースが必要となる実時間の量が増加します。これはキャッシュのパフォーマンスに悪影響を与える可能性があり、他の誰かが同じリソースを使用するための排他的権利を必要とする場合は明らかに悪いことです。

于 2012-09-21T18:34:23.177 に答える