C / C ++と競合する高品質のLISP / Schemeコンパイラ


8

理論的に言えば、コンパイルされたCと競合できるコードを生成できるLisp / Schemeコンパイラを使用することは可能でしょうか。

私のテストでは、現在のコンパイラ(Bigloo、SBCL、Gambit、Chickenなど)が同等のCコードより20〜50倍遅いことがわかりました。

のみ外れ値スターリンコンパイラです。単純なプログラムの場合は、Cと同等のバイナリが生成されます。しかし、疑わしいのは、他のプロジェクト(Bigloo、Chicken、Clozureなど)が、スターリンが使用するトリック(「プログラム全体の最適化」)の実装を試みていないことです。等)。

私は90年代半ばからLISPの大ファンで、C / C ++ /。NET / etcを使用して通常の半分の時間でプロジェクトを実行できるようにしたいのですが...パフォーマンス問題は大きな障害です。

質の高いLISPコンパイラが不足しているのは、深刻な時間とお金がこのテーマに費やされていないためか、コンパイラテクノロジの現状を考えると、これは単に実行可能なタスクではないのでしょうか。


1
Common Lispコンパイラを(declare (optimize ...))(declare (<type> <var))および(the <type> <expr>)関数でテストしたと思いますか?そうでなければ、それはほとんど公平な比較ではありません:)
JakubLédlFeb

1
私はcs.stackexchange.com/questions/842/…がこの質問に答えると思います。
カイルジョーンズ

@KyleJonesしますか?私の推測では、最大限の最適化を行うと、Common LispはOPで指定されたマージン内に収まる可能性があります。
JakubLédl2013

プログラミング言語を変更するだけでは、チームは正しいコードを同時に4倍に増やすことはできません。研究が示したことは、言語に依存しないプログラマーは、言語に関係なく、問題の複雑さを解決するために、単位時間あたりほぼ同じ数のコード行を出力するということです。したがって問題の領域でLISPプログラムがはるかに短い場合を除いて何も得られません。考慮すべきもう1つのことは、開発と保守のためにLISPの経験を積む必要があることです。そして、それらは中間にあります。
フォンブランド2013

1
この質問は、科学的な答えよりもプログラマーの経験を求めているようです。したがって、これは質問に対して間違ったサイトである可能性があります。
ラファエル

回答:


7

コメントに記載されているように、「現在のコンパイラ(Bigloo、SBCL、Gambit、Chickenなど)は、同等のCコードよりも20〜50倍遅い」と述べるのは正確ではありません。あなたがテストした。

以下のために私の使用、私は多くの事のためギャンビットとチキンスキームは、一般的に高速チキン(4.9.0.1)よりしかしギャンビット(4.7.3)の現在のバージョンでは、非常に近い速度で同等の「C」コードにしていることがわかり、事前オーバー-最適化出力「C」コード(利用可能なレジスタの数を想定-x686を想定)-追加のメモリ要件に対してスタックメモリの使用を強制します。この決定は、Chickenが行うように 'C'コンパイラに任せる必要があります。 「C」コンパイラーが独自の最適化を実行しないようにするための追加のレジスターと結合処理ステップの要件)非常にタイトな小さなループが「C」(またはチキン)の同じタイトな小さなループの約2倍の速度になる); Chickenは、与えられた関数にローカルとなる変数を定義します(ほとんどの場合不変に使用されます)。そして、コンパイラーに依存してそれらのほとんどを最適化します。鶏肉は

EDIT_ADD: 私はChickenおよびGambit-Cスキームバージョンについてさらに調査を行い、次のことを発見しました。

  1. 上記の理由により、小さなタイトなループの場合、ChickenはGambitよりも高速です(最適化は 'C'コンパイラーに依存し、それ自体は何もしないため、x86-64の余分なレジスターをより有効に活用します)。ループ内の「POLL」スタックメンテナンスチェック。Gambitはループ内の「POLL」チェックを含みます。これがトリガーされない場合(通常の場合)でも、何も必要でないと判断するのに数CPUクロックサイクルかかります(約6サイクル)。将来のよりスマートなコンパイラは、タイトなループの内側でスタック構築操作を行わずにスタックチェックを行う必要がないことを確認し、ループの直前または直後に行うことで、この時間を節約できます。

  2. Gambitの「C」マクロは、特に固定スタックサイズの演算を含め、演算の実行方法を正確に定義する上で、あまりにも多くの作業を行います。これらは、「C」コンパイラが最適化するのが難しい可能性があります。レジスタをより効果的に使用すると、タイトなループ時間をおそらく4サイクル削減できます。

  3. いずれも、適切な変更が行われ、コンパイラーが実行するようにコードを出力しない、たとえばベクター操作の「読み取り/変更/書き込み」最適化を出力しません。LLVMなどのよりスマートなバックエンドをHaskellで使用すると、このようなことが行われます。これにより、個別の読み取り、変更の計算、および同じ場所への書き込みではなく、単一の命令のみを使用する場合のレジスタの使用と実行時間が1つ削減されます。これは、変更(たとえば、または少し)の計算になり、次に読み取り変更(| =)書き込み単一命令になります。これにより、速度がサイクル程度速くなる可能性があります

  4. どちらも動的に型指定され、データの「タグ」ビットをデータの一部として処理します。タイトなループがタグを削減し、「タグなし」でループを実行してから、タグをループからの結果に追加するのに十分なほどスマートではありません。場合によっては、これらの操作を組み合わせます。ここでの最適化により、ループにもよりますが、CPUサイクル数回程度ループ時間を短縮できます。

  5. 非常にタイトな「C」ループは、メモリキャッシュアクセス速度が抑制されていない高速のCPU(AMD Bulldozerなど、約2倍遅い)では、ループごとに約3.5 CPUクロックサイクルかかる可能性があります。Chickenの同じループは現在約6サイクルかかり、Gambitは約16.9サイクルかかります。上記のすべての最適化により、Schemeのこれらの実装がそれを実行できなかった理由はありませんが、いくつかの作業が必要です。

  6. Gambitの場合、より困難な作業は、「POLL」テストを挿入する必要がない場合を認識するようにフロー分析を改善することです(つまり、これは割り込み駆動である可能性がありますが、コンパイラーは一部の用途で割り込みをオフにすることを許可しますか? ); より簡単な改善は、より良いレジスタ使用法を実装することです(つまり、デフォルトのx686アーキテクチャよりもx86-64レジスタをよりよく認識します)。どちらの場合も、フロー分析は、データ、特に「fixnum」、「flonum」、およびベクトルのデータの「タグ付けを解除」できることを認識し、これらの操作がタイトなループ内で必要なく、読み取り/変更/書き込み命令を組み合わせる必要がないことを認識します。これらの目的は両方とも、LLVMなどのより優れたバックエンドを使用することで達成できます(些細な作業ではありませんが、両方とも既に部分的に存在しています)。

結論:チキンは現在、最速のCPUで「C」よりも約50%遅く(「ブルドーザー」ではなく、「C」コードのキャッシュスロットリングによりほぼ同じ速度です)、Gambitは約400%遅くなっています(約遅いブルドーザーでは125%遅くなります)。ただし、コンパイラの将来の改善により、コードが「C」より遅くならないように、またはOPが指定するマージン内に収まるように、これを減らすことができます。

Haskellなどのより複雑な言語。LLVMバックエンドを使用するときは、厳格な使用(デフォルトでは常に熱心なスキームの問題ではない)に注意を払い、適切なデータ構造(タイトループのリストではなくSTボックス化されていない配列)を使用します。スキームをベクトルを使用して多少適用可能)、LLVMバックエンドが完全な最適化で使用されている場合、「C」とほぼ同じ速度で実行されます。これが可能であれば、Schemeでも上記のコンパイラーの改善を行うことができます。

再び、これらのいずれかが適切な最適化フラグと一緒に使用された場合、20倍から50倍遅いということはありません。END_EDIT_ADD

もちろん、本番環境の場合のように適切な最適化設定を使用しないと、すべてのベンチマークが無効になります...

商用のChez Schemeコンパイラは、GambitやChickenと同じように高パフォーマンスの出力を生み出すのに大いに役立つと思います。商用であることには確かに「かなりの時間とお金が費やされている」からです。

GambitまたはChickenを「Cの20〜50倍」の速度で実行できる唯一の方法は、最適化設定を使用しないことです。この場合、REPLで解釈されるよりも速く実行されないことがよくあります。 -これらの設定を適切に使用するよりも数十倍遅い。

OPがこれらの設定を適切に使用してテストされていない可能性はありますか?

OPがテスト手順を明確にすることに関心がある場合は、この回答を喜んで編集して、少なくともGambitとChickenがそれほど遅くなくてもよいことを示します。


これはランタイム型チェックが無効になっていますか?以前はできなかったバグを悪用可能にするため、本番環境ではそれを行いたくありません。
デミ

@Demetriは、Gambit-C、Chicken、BiglooなどのほとんどのSchemeコンパイラで、すべてのランタイムチェックを無効にすることにより、多くのベンチマークで約3倍のスピードアップを実現しますが、コードは「20〜50倍遅い」わけではありません。 OPの質問で述べたように。実際、これらのチェックの多くは、必要な場合にのみコードに組み込まれるようにコードを作成することにより、リスクを冒すことなくデバッグの問題をチェックした後、プロダクションコードで安全に無効にできます。
GordonBGood

@Demetri最適化を使用してコンパイルした場合、チキンスキームがベンチマークコードのCより1.5〜2.5倍遅い範囲にあることを証明できます。しかし、そうです、最適化せずにコンパイルすると、ひどく遅くなります。FWIW私は、fixnum-ops、unboxed-objects、およびブロックのコンパイルを許可することで最良の結果を得ています。つまり、静的分析を改善することで、最適化を改善しています。
Morten Jensen

ランタイムの安全性チェックについてもっと心配しています-テストで現れなかったバグが悪用可能なバッファオーバーフローになることを望んでいません。明らかに、最適化をオンにします。
Demi

@Demetri理解できる。私の経験では、ランタイムチェックのオーバーヘッドは、記述するコードの種類に大きく依存します。時々、オーバーヘッドは私のテストでチェックなしで実行する場合の10倍以上になります。
Morten Jensen
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.