コンパイラの時間の複雑さ


54

コンパイラの時間の複雑さに興味があります。考慮すべきコンパイラ、コンパイラオプション、変数が多数あるため、これは明らかに非常に複雑な質問です。具体的には、LLVMに興味がありますが、人々が持っていた考えや研究を始める場所に興味があります。かなりグーグルはほとんど何も明らかにしないようです。

私の推測では、指数関数的ですが、実際の時間にはほとんど影響しない最適化手順がいくつかあると思います。たとえば、数値に基づく指数関数は関数の引数です。

私の頭の上から、私はASTツリーの生成は線形であると言うでしょう。IR生成では、増加し続けるテーブルの値を検索しながらツリーをステップスルーする必要があるため、またはO n log n )となります。コードの生成とリンクは、同様のタイプの操作になります。したがって、現実的に成長しない変数の指数関数を削除した場合、私の推測はO n 2になります。O(n2)O(nlogn)O(n2)

私は完全に間違っている可能性があります。誰もそれについて何か考えがありますか?


7
「指数」、「線形」、、またはO n log n )であると主張するときは注意する必要があります。少なくとも私にとっては、入力をどのように測定するかはまったく明らかではありません(指数関数は何ですか?nは何を表していますか?)O(n2)O(nlogn)n
Juho 14年

2
LLVMと言うとき、Clangのことですか?LLVMは、いくつかの異なるコンパイラサブプロジェクトを持つ大きなプロジェクトであるため、少しあいまいです。
ネイトCK 14年

5
C#の場合、最悪の場合の問題では少なくとも指数関数的です(C#でNP完全SAT問題をエンコードできます)。これは単なる最適化ではなく、関数の正しいオーバーロードを選択するために必要です。C ++のような言語の場合、テンプレートは完全に調整されているため、決定できません。
CodesInChaos

2
@Zane私はあなたのポイントを理解していません。テンプレートのインスタンス化はコンパイル中に発生します。正確な出力を生成するためにコンパイラにその問題を強制的に解決させる方法で、難しい問題をテンプレートにエンコードできます。コンパイラは、完全なテンプレートプログラミング言語のインタープリターと考えることができます。
CodesInChaos

3
C#のオーバーロードの解決は、複数のオーバーロードをラムダ式と組み合わせる場合、かなり注意が必要です。これを使用して、ブール式をそのような方法でエンコードし、適用可能な過負荷があるかどうかを判断するにはNP完全3SAT問題が必要になります。問題を実際にコンパイルするには、コンパイラはその式の解決策を実際に見つける必要がありますが、それはさらに難しい場合があります。エリック・リッパートは、彼のブログ記事Lambda Expressions vs. Anonymous Methods、パート5
CodesInChaos

回答:


50

質問に答えるのに最適な本は、おそらくCooper and Torczon、 "Engineering a Compiler、" 2003です。大学図書館にアクセスできる場合は、コピーを借りることができます。

O(n2)n

O(n)O(n)

O(n)O(1)O(s)s

次に、解析ツリーは通常、制御フローグラフに「フラット化」されます。制御フローグラフのノードは3アドレス命令(RISCアセンブリ言語に類似)である可能性があり、制御フローグラフのサイズは通常、解析ツリーのサイズに比例します。

O(d)dO(n)n

より高度な最適化を行うには、より高度な分析を行うことをお勧めします。この時点で、トレードオフに陥り始めます。解析アルゴリズムによりもはるかに少ない時間がかかることを望むO(n2)プログラム全体のフローグラフのサイズの時間ですが、これは、証明するのに費用がかかる情報(およびプログラムを改善する変換)なしで行う必要があることを意味します。この典型的な例はエイリアス分析です。ここでは、メモリ書き込みのいくつかのペアについて、2つの書き込みが同じメモリ位置を決してターゲットにできないことを証明したいと考えています。(エイリアス分析を実行して、1つの命令を他の命令の上に移動できるかどうかを確認することもできます。)しかし、エイリアスに関する正確な情報を取得するには、プログラム内のすべての制御パスを分析する必要があります。プログラム内(したがって、制御フローグラフ内のノード数の指数関数的)

次に、レジスタの割り当てに入ります。レジスターの割り当ては、グラフの色付けの問題として表現することができ、最小数の色でグラフを色付けすることはNPハードであることが知られています。そのため、ほとんどのコンパイラーは、レジスターのスピル数を合理的な時間内にできる限り減らすことを目的に、レジスターのスピルと組み合わせたある種の貪欲なヒューリスティックを使用します。

最後に、コード生成に入ります。コード生成は通常、一度に最大の基本ブロックで実行されます。この場合、基本ブロックは、単一のエントリと単一の出口を持つ線形接続された制御フローグラフノードのセットです。これは、カバーしようとしているグラフが基本ブロック内の3アドレス命令のセットの依存グラフであり、使用可能なマシンを表すグラフのセットでカバーしようとしている問題をカバーするグラフとして再定式化できます。指示。この問題は、最大の基本ブロックのサイズが指数関数的であるため(原則として、プログラム全体のサイズと同じ順序になる可能性があります)、これは通常、可能性のあるカバーのごく一部のみが発見的手法で行われます調べた。


4
サード!ちなみに、コンパイラが解決しようとする問題(レジスタの割り当てなど)の多くはNPハードですが、他の問題は正式には決定できません。たとえば、呼び出しp()の後に呼び出しq()があるとします。pが純関数の場合、p()が無限ループしない限り、呼び出しを安全に並べ替えることができます。これを証明するには、停止問題を解決する必要があります。NPの困難な問題と同様に、コンパイラの作成者は、可能な限りソリューションを近似するために、できるだけ多くの労力を費やすことができます。
仮名14年

4
ああ、もう一つ:理論的には非常に複雑なタイプシステムが現在使用されています。Hindley-Milner型推論はDEXPTIME-completeに知られており、MLに似た言語はそれを正しく実装する必要があります。ただし、a)実際のプログラムでは病理学的なケースが発生することはなく、b)実際のプログラマーはより良いエラーメッセージを得るためだけに型注釈を付ける傾向があるため、実行時間は実際には線形です。
仮名14年

1
いい答え、欠けていると思われる唯一のものは、説明の簡単な部分で、簡単な言葉で綴られています:プログラムのコンパイルはO(n)でできます。最新のコンパイラが行うように、コンパイルする前にプログラムを最適化することは、事実上無制限のタスクです。実際にかかる時間は、タスクの固有の制限によって支配されるのではなく、人々が待つのに飽きる前のある時点でコンパイラが終了する実際的な必要性によって支配されます。それは常に妥協です。
aaaaaaaaaaaa 14年

@Pseudonym、コンパイラが何度も停止問題(または非常に厄介なNPハード問題)を解決する必要があるという事実は、標準が未定義の動作が発生しないと仮定する際にコンパイラ作成者に余裕を与える理由の1つです(無限ループなど) )。
フォンブランド14年

15

実際、一部の言語(C ++、Lisp、Dなど)はコンパイル時にチューリング完全であるため、一般にコンパイルは決定できません。C ++の場合、これはテンプレートの再帰的なインスタンス化が原因です。LispとDの場合、コンパイル時にほとんどすべてのコードを実行できるため、必要に応じてコンパイラを無限ループにスローできます。


3
Haskell(拡張機能付き)およびScalaの型システムもチューリング完全です。つまり、型チェックには無限の時間がかかる可能性があります。現在、Scalaにはチューリング完全マクロもあります。
ヨルグWミットタグ14年

5

C#コンパイラの実際の経験から、特定のプログラムでは、出力バイナリのサイズは入力ソースのサイズに対して指数関数的に増加する(これは実際にはC#仕様で必要であり、削減できない)と言えます。少なくとも指数関数でなければなりません。

C#の一般的なオーバーロード解決タスクはNP困難であることが知られています(実際の実装の複雑さは少なくとも指数関数的です)。

C#ソースでのXMLドキュメントコメントの処理には、コンパイル時に任意のXPath 1.0式を評価することも必要です。


C#バイナリがそのように爆発する原因は何ですか?私には、言語のバグのような音が...
vonbrand

1
ジェネリック型がメタデータでエンコードされる方法です。class X<A,B,C,D,E> { class Y : X<Y,Y,Y,Y,Y> { Y.Y.Y.Y.Y.Y.Y.Y.Y y; } }
ウラジミールレシェトニコフ14年

-2

オープンソースプロジェクトのセットなど、現実的なコードベースで測定します。結果を(codeSize、finishTime)としてプロットすると、それらのグラフをプロットできます。データf(x)= yがO(n)である場合、データが大きくなり始めた後、g = f(x)/ xをプロットすると直線が得られます。

f(x)/ x、f(x)/ lg(x)、f(x)/(x * lg(x))、f(x)/(x * x)などをプロットします。ゼロまでオフ、無制限に増加、または平坦化。このアイデアは、空のデータベースから開始する挿入時間を測定するような場合に役立ちます(つまり、長期にわたって「パフォーマンスリーク」を探すため)。


1
実行時間の経験的な測定は、計算の複雑さを確立しません。第一に、計算の複雑さは、最悪の場合の実行時間に関して最も一般的に表されます。第二に、何らかの平均的なケースを測定したい場合でも、その意味で入力が「平均」であることを確認する必要があります。
デビッドリチャービー14年

確かに、それは単なる推定値です。しかし、大量の実データを使用した単純な経験的テスト(多数のgitリポジトリに対するすべてのコミット)は、慎重なモデルに勝る可能性があります。いずれにせよ、関数が実際にO(n ^ 3)であり、f(n)/(n n n)をプロットすると、勾配がほぼゼロのノイズのあるラインが得られます。O(n ^ 3)/(n * n)のみをプロットした場合、直線的に上昇することがわかります。過大評価して、ラインが急速にゼロに急降下するのを見れば、それは本当に明らかです。
ロブ14年

1
Θ(nlogn)Θ(n2)Θ(nlogn)Θ(n2)

リアルタイムの重要な入力解析を行って、不正な入力を行う攻撃者からサービス拒否を取得することを心配している場合、あなたが知る必要があることに同意します。コンパイル時間を測定する実際の関数は非常にうるさくなり、私たちが気にするケースは実際のコードリポジトリになります。
ロブ14年

1
いいえ。質問は問題の時間的な複雑さについて尋ねます。これは通常、最悪の場合の実行時間であると解釈されますが、これはリポジトリ内のコードの実行時間ではありません。提案するテストは、コンパイラが特定のコードを処理するのにどれくらいの時間を要するかについて合理的なハンドルを提供します。これは、知っておくと便利なことです。しかし、彼らは問題の計算の複雑さについてほとんど何も教えてくれません。
デビッドリチャービー14年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.