26

サイモン・ペイトン・ジョーンズらが執筆した論文を読んでいました。「Playing by the Rules: GHC における実用的な最適化手法としての書き換え」 . 2番目のセクション、つまり「基本的な考え方」で、彼らは次のように書いています。

mapリストの各要素に関数を適用するおなじみの関数を考えてみましょう。Haskell で書くと、map次のようになります。

map f []     = []
map f (x:xs) = f x : map f xs

ここで、コンパイラが の次の呼び出しに遭遇したとしますmap

map f (map g xs)

この式が

map (f . g) xs

(「.」は関数合成)、中間リストがないため、後者の式が前者よりも効率的であることがわかります。しかし、コンパイラにはそのような知識はありません。

考えられる反論の 1 つは、コンパイラはより賢くあるべきだというものです --- しかし、プログラマは、コンパイラが理解できないことを常に知っています。別の提案は次のとおりです。プログラマーがそのような知識をコンパイラーに直接伝達できるようにします。それが私たちがここで探求する方向です。

私の質問は、コンパイラをよりスマートにできないのはなぜですか? 著者は、「しかし、プログラマーは、コンパイラーが把握できないことを常に知っている」と述べています。map f (map g xs)ただし、コンパイラはが と同等であることを実際に把握できるため、これは有効な答えではありません。そのmap (f . g) xs方法は次のとおりです。

map f (map g xs)
  1. map g xsと一体化map f [] = []

    したがってmap g [] = []

  2. map f (map g []) = map f [].

    map f []と一体化map f [] = []

    したがってmap f (map g []) = []

  3. map g xsと一体化map f (x:xs) = f x : map f xs

    したがってmap g (x:xs) = g x : map g xs

  4. map f (map g (x:xs)) = map f (g x : map g xs).

    map f (g x : map g xs)と一体化map f (x:xs) = f x : map f xs

    したがってmap f (map g (x:xs)) = f (g x) : map f (map g xs)

したがって、次のルールがあります。

map f (map g [])     = []
map f (map g (x:xs)) = f (g x) : map f (map g xs)

ご覧のとおり、f (g x)ちょうど再帰的に呼び出されています(f . g)map f (map g xs)これはまさに の定義ですmap (f . g) xs。この自動変換のアルゴリズムはかなり単純なようです。では、ルールを書き換える代わりにこれを実装してみませんか?

4

3 に答える 3

34

アグレッシブなインライン化は、リライト ルールの省略形である等式の多くを導き出すことができます。違いは、インライン化は「ブラインド」であるため、結果が良くなるのか悪くなるのか、あるいは終了するかどうかさえ事前にわからないことです。

ただし、書き換えルールは、プログラムに関するより高いレベルの事実に基づいて、完全に非自明なことを行うことができます。書き換えルールは、新しい公理をオプティマイザーに追加するものと考えてください。これらを追加することで、適用するルール セットが豊富になり、複雑な最適化を適用しやすくなります。

たとえば、Stream fusionはデータ型の表現を変更します。これは、表現型の変更を伴うため、インライン化では表現できません ( StreamADT の観点から最適化問題を再構成します)。書き換えルールで言いやすい、インライン化だけでは無理。

于 2014-11-09T11:42:04.110 に答える
7

その方向の何かが、私の学生であるJohannes Baderの学士論文で調査されました。

確かにある程度は可能ですが、

  • それはかなりトリッキーです。そのような方程式を見つけることは、ある意味では、定理証明で証明を見つけるのと同じくらい難しいです。
  • プログラマーが直接書くことはめったにない方程式を見つける傾向があるため、あまり役に立ちません。

ただし、インライン化やさまざまな形式の融合など、他の変換後にクリーンアップすると便利です。

于 2014-11-09T13:14:20.800 に答える
1

これは、特定のケースでの期待のバランスと、一般的なケースでの期待のバランスとのバランスと見なすことができます。このバランスは、何かをより速くする方法を知っているというおかしな状況を生み出す可能性がありますが、知らない方が一般的に言語にとっては良いことです。

指定した構造のマップの特定のケースでは、コンピューターは最適化を見つけることができます。しかし、関連する構造はどうでしょうか。関数がマップでない場合はどうなりますか? map を返す関数など、間接的なレイヤーが追加されている場合はどうなるでしょうか。そのような場合、コンパイラは簡単に最適化できません。これは一般的なケースの問題です。

特殊なケースを最適化すると、2 つの結果のうちの 1 つがどのように発生するか

  • そこにあるかどうかわからないので、誰もそれに頼っていません。この場合、引用したような記事が書かれます
  • 人々はこれに頼り始めており、今ではすべての開発者が「この構成で作成されたマップは高速バージョンに自動的に変換されますが、この構成で実行すると変換されない」ことを覚えておく必要があります。これは、人々が言語を使用する方法を操作し始め、実際に読みやすさを低下させる可能性があります!

開発者が一般的なケースでそのような最適化について考える必要があることを考えると、開発者がこれらの最適化を単純なケースで行うことを期待し、そもそも最適化の必要性を減らします!

ここで、関心のある特定のケースが Haskell の世界のコードベースの 2% のような大規模なものを占めていることが判明した場合、特別なケースの最適化を適用するためのより強力な議論があるでしょう。

于 2014-11-09T20:06:40.040 に答える