λ-calculus:関数のメモリ表現で最も効率的なものは何ですか?


9

関数エンコード(チャーチ/スコット)と従来エンコード(アセンブラ/ C)のデータ構造のパフォーマンスを比較したいと思います。

しかし、その前に、メモリ内の関数表現がどれほど効率的/であるかを知る必要があります。もちろん、関数は部分的に適用することもできます(別名、クロージャ)。

現在の一般的な関数型言語(Haskell、ML)のエンコードアルゴリズムの使用と、達成可能な最も効率的なアルゴリズムの両方に興味があります。


ボーナスポイント:マップが(ネイティブ整数にエンコードされた整数を機能するようなエンコーディングありshortintCでなどで)。可能ですか?


パフォーマンスに基づいて効率を評価します。言い換えれば、エンコーディングが効率的であるほど、関数型データ構造を使用した計算のパフォーマンスへの影響は少なくなります。


私のGoogleの試みはすべて失敗しました。おそらく正しいキーワードがわかりません。
フォードO.

質問を編集して、「効率的」の意味を明確にできますか?何のために効率的ですか?効率的なデータ構造を求める場合は、データ構造の選択に影響を与えるため、データ構造に対して実行できる操作を指定する必要があります。それとも、エンコーディングは可能な限りスペース効率が良いということですか?
DW

1
これはかなり広いです。ラムダ計算を効率的に実行することを目的とした多くの抽象マシンがあります(たとえば、SECD、CAM、Krivine's、STGを参照)。さらに、Church / Scottでエンコードされたデータを考慮する必要があります。これは、さらに問題を引き起こします。たとえば、教会でエンコードされたリストでは、テール操作はO(1)ではなくO(n)でなければなりません。私はどこかで、O(1)の頭と尾の操作を伴うシステムFのリストのエンコーディングの存在が未解決の問題であったことを読んだと思います。
2017年

@DW私はパフォーマンス/オーバーヘッドについて話しています。たとえば、教会のリストとHaskellのリストに対する効率的なエンコーディングマッピングでは、同じ時間がかかるはずです。
フォードO.

どの操作のパフォーマンス?関数で何をしたいですか?これらの関数をいくつかの値で評価しますか?一度、または多くの値で同じ関数を評価しますか?それらで他に何かしますか?可能な限り効率的に実行できるように、関数(関数型言語で書かれた)をコンパイルする方法を尋ねているだけですか?
DW

回答:


11

実は、関数のエンコードに関しては、あまり余裕がありません。主なオプションは次のとおりです。

  • 用語の書き換え:関数を抽象構文ツリー(またはその一部のエンコード)として保存します。関数を呼び出すときは、構文ツリーを手動で走査して、パラメーターを引数に置き換えます。これは簡単ですが、時間と空間の点で非常に非効率的です。 。

  • クロージャー:関数、おそらく構文ツリーを表現する何らかの方法があります。これらの関数では、何らかの方法で参照によって引数を参照します。ポインタオフセット、整数、またはDe Bruijnインデックス、名前の場合があります。次に、関数をクロージャとして表現します。関数の「命令」(ツリー、コードなど)は、関数のすべての自由変数を含むデータ構造とペアになります。関数が実際に適用されると、環境、ポインター演算などを使用して、データ構造内の自由変数を検索する方法がどういうわけかわかります。

他のオプションもあると思いますが、これらは基本的なオプションです。他のほとんどすべてのオプションは、基本的なクロージャー構造のバリアントまたは最適化になると思います。

したがって、パフォーマンスの点では、クロージャはほとんどの場合、用語の書き換えよりも優れたパフォーマンスを発揮します。バリエーションのうち、どちらが良いですか?それはあなたの言語とアーキテクチャに大きく依存しますが、「無料の変数を含む構造体を持つマシンコード」が最も効率的だと思います。関数が必要とするすべてのもの(命令と値)があり、それ以外には何もありません。また、呼び出しが大量の用語トラバーサルを実行することにはなりません。

現在のエンコードアルゴリズムの人気のある関数型言語(Haskell、ML)の両方の使用に興味があります

私はエキスパートではありませんが、99%です。ほとんどのMLフレーバーは、説明するクロージャーのバリエーションを使用していますが、最適化は行われている可能性があります。(おそらく古くなっている)視点については、これを参照してください。

Haskellは遅延評価のため、もう少し複雑な処理を行います。SpinelessTagless Graph Rewritingを使用します。

また、達成できる最も効率的な方法でも。

最も効率的なものは何ですか?すべての入力にわたって最も効率的な実装はないため、平均して効率的な実装が得られますが、それぞれが異なるシナリオで優れています。したがって、最も効率的または最も効率的であるという明確なランキングはありません。

ここには魔法はありません。関数を格納するには、何らかの方法でその自由な値を格納する必要があります。そうしないと、関数自体よりも少ない情報をエンコードします。おそらく、部分的な評価でいくつかの無料の値を最適化することができますが、それはパフォーマンスにリスクがあり、これが常に停止するように注意する必要があります。

そして、おそらくスペースの効率を上げるために、ある種の圧縮または巧妙なアルゴリズムを使用することができます。しかし、それは時間をスペースと交換するか、または一部のケースでは最適化し、他のケースでは速度を落とした状況にあります。

一般的なケースに合わせて最適化できますが、一般的なケース、言語、アプリケーションの領域などによって変わる可能性があります。ビデオゲームで高速なコードの種類(数値の計算、大量の入力を伴うタイトなループ)は、おそらくコンパイラーの高速化(ツリートラバーサル、ワークリストなど)。

ボーナスポイント:関数エンコードされた整数をネイティブ整数(Cのshort、intなど)にマップするようなエンコードはありますか?可能ですか?

いいえ、できません。問題は、ラムダ計算では用語を内省できないことです。関数がChurch-numeralと同じ型の引数を取る場合、その数値の正確な定義を調べなくても、関数を呼び出すことができる必要があります。それがChurchエンコーディングでのことです。それらを使用して実行できる唯一のことはそれらを呼び出すことであり、これを使用して便利なすべてをシミュレートできますが、コストがかかりません。

さらに重要なことに、整数は可能なすべてのバイナリエンコーディングを占有します。したがって、ラムダが整数として表されている場合、チャーチ番号以外のラムダを表す方法はありません。または、ラムダが数値であるかどうかを示すフラグを導入しますが、必要な効率はウィンドウの外に出る可能性があります。

編集:これを書いて以来、高次関数を実装するための3番目のオプションである機能化に気づきました。ここでは、switchどのラムダ抽象化が関数として与えられたかに応じて、すべての関数呼び出しが大きなステートメントになります。ここでのトレードオフは、それがプログラム全体の変換であることです。事前にラムダ抽象化の完全なセットを用意する必要があるため、パーツを個別にコンパイルしてからこのようにリンクすることはできません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.