Ruby / Pythonのような動的言語は、パフォーマンスのようなC / C ++に到達できますか?


64

Rubyのような動的言語用のコンパイラをビルドして、C / C ++と同等の性能を比較することは可能でしょうか?私がコンパイラについて理解していることから、Rubyを例にとると、Rubyがリフレクションを処理する方法、整数から大きな整数への自動型変換などの機能、および静的型付けの欠如が効率的なコンパイラを構築するため、Rubyコードのコンパイルは効率的ではありませんRubyの場合は非常に困難です。

Rubyやその他の動的言語をC / C ++に非常に近い性能を発揮するバイナリにコンパイルできるコンパイラを構築することは可能ですか?PyPy / RubiniusなどのJITコンパイラが最終的にパフォーマンスでC / C ++と一致する、または一致しないという根本的な理由はありますか?

注:「パフォーマンス」はあいまいになる可能性があることを理解しているので、それを明確にするために、パフォーマンスYでC / C ++でXを実行できる場合、Yに近いパフォーマンスでRuby / PythonでXを実行できますか?Xは、デバイスドライバーやOSコードからWebアプリケーションまですべてです。


1
他の人よりも適切な証拠に裏打ちされた回答を促すように、質問を言い換えることはできますか?
ラファエル

@Raphael先に進んで編集しました。私の編集は、質問の意味を根本的に変えるものではなく、意見を寄せ付けないものにすると思います。
ジル 'SO-悪であるのをやめる

1
特に、1つ(または少数)の具体的なパフォーマンス測定値を修正する必要があると思います。ランタイム?スペース使用量?エネルギー使用量?開発者の時間は?投資の回収?また、この質問に関連するメタに関するこの質問(またはその回答)にも注意してください。
ラファエル

この質問は、宗教戦争の典型的なイニシエーターです。そして、答えからわかるように、非常に文明化されたものではありますが、私たちはそれを持っています。
アンドレイバウアー

オプションの型注釈を許可する動的言語があります(例:Clojure)。私が知っていることから、型注釈付き関数に関連するパフォーマンスは、言語が静的に型付けされる場合と同等です。
ペドロ・モルテロロ

回答:


68

「はい」と言ったすべての人に、設計上、答えが「いいえ」であるという反論点を提示します。これらの言語は、静的にコンパイルされた言語のパフォーマンスと一致することはありません。

Kosは、動的言語には実行時のシステムに関する詳細情報があり、コードを最適化するために使用できるという(非常に有効な)ポイントを提供しました。

ただし、コインにはもう1つの側面があります。この追加情報を追跡する必要があります。現代のアーキテクチャでは、これはパフォーマンスを犠牲にします。

ウィリアムエドワーズは議論の素晴らしい概要を提供します

特に、Kosが言及した最適化は、Devinが言及したように、言語の表現力を大幅に制限しない限り、非常に限られた範囲を超えて適用することはできません。これはもちろん実行可能なトレードオフですが、議論のために、動的言語ではなく静的言語になります。これらの言語は、ほとんどの人が理解できるように、PythonやRubyとは根本的に異なります。

ウィリアムは、興味深いIBMスライドをいくつか引用しています。

  • すべての変数は動的に型指定できます:型チェックが必要です
  • すべてのステートメントは、型の不一致などにより例外をスローする可能性があります。例外チェックが必要です
  • すべてのフィールドとシンボルは、実行時に追加、削除、変更できます:アクセスチェックが必要です
  • すべてのオブジェクトのタイプとそのクラス階層は実行時に変更できます:クラス階層のチェックが必要です

いくつかのこれらのチェックのは、分析後に除去することができます(NB:この分析は、また時間がかかる-実行時)。

さらに、コス氏は、動的言語がC ++のパフォーマンスを上回る可能性があるとも主張しています。JITは実際にプログラムの動作を分析し、適切な最適化を適用できます。

しかし、C ++コンパイラでも同じことができます!最新のコンパイラーは、いわゆるプロファイルガイド最適化を提供します。これは、適切な入力が与えられると、プログラム実行時の動作をモデル化し、JITが適用するのと同じ最適化を適用できます。

もちろん、これは現実的なトレーニングデータの存在にかかっています。さらに、使用パターンが実行中に変更された場合、プログラムは実行時の特性を調整できません。JITはこれを理論的に処理できます。最適化を切り替えるために、JITは継続的に使用率データを収集する必要があるため、実行が再び遅くなるため、この運賃が実際にどのように行われるかを見てみたいと思います。

要約すると、静的分析と最適化と比較して、実行時のホットスポットの最適化は、長期的には実行時情報を追跡するオーバーヘッドを上回るとは思いません


2
@Raphaelこれは、コンパイラの「欠点」です。特に、javacプロファイルに基づく最適化はこれまでにありましたか?私の知る限りではありません。一般に、JITがJITted言語を処理できるため、JIT化された言語のコンパイラを最適化することは意味がありません(少なくとも、この方法でより多くの言語が利益を得ます)。そのため、(理解できるように)javac私が知る限り、最適化プログラムに大きな労力を費やすことはありませんでした(.NET言語の場合、これは間違いなく真実です)。
コンラッドルドルフ

1
@Raphaelキーワードは:多分。どちらにも表示されません。私が言いたかったのはそれだけです。前のパラグラフでの私の仮定の理由(しかし証拠はありません)を与えました。
コンラッドルドルフ

1
@ベン私はそれが複雑であることを否定しません。これは単なる直感です。結局のところ、実行時にすべての情報を追跡するにはコストがかかります。IOに関するあなたの主張に納得していません。これが予測可能な場合(=典型的なユースケース)、PGOはそれを予測できます。それが偽物である場合、JITがそれを最適化できるとは確信していません。たまに運が悪かったのかもしれません。しかし、確実に?…
コンラッドルドルフ

2
@コンラッド:あなたの直感は間違っています。実行時に変化することではなく、コンパイル時に予測できないことです。JITと静的最適化のスイートスポットは、実行時にプログラムの動作がプロファイリングの「速すぎる」ときに変化する場合ではなく、プログラムの個々の実行でプログラムの動作が最適化されやすいが、実行します。通常、静的オプティマイザーは1セットの条件のみを最適化する必要がありますが、JITは各実行を個別に最適化し、その実行で発生する条件を最適化します。
ベン

3
注意:このコメントスレッドは、ミニチャットルームに発展しています。この議論を続けたい場合は、チャットに持って行ってください。元の投稿を改善するには、コメントを使用する必要があります。ここでこの会話を中断してください。ありがとう。
ロバートCartaino

20

パフォーマンスYでC / C ++でXを実行できる場合、Yに近いパフォーマンスでRuby / PythonでXを実行できますか?

はい。例として、PyPyを取り上げます。これは、Cに近い解釈を実行するPythonコードのコレクションです(それほど近くではありませんが、遠く離れているわけでもありません)。これを行うには、ソースコードでフルプログラム分析を実行して各変数に静的型を割り当てます(詳細については、AnnotatorおよびRtyperのドキュメントを参照してください)。その後、Cに与えた同じ型情報で武装すると、同じ最適化の種類。少なくとも理論的には。

トレードオフはもちろん、PythonコードのサブセットのみがRPythonによって受け入れられることであり、一般に、その制限が解除されても、Pythonコードのサブセットのみがうまくいくことができます:分析して静的型を指定できるサブセットです。

Pythonを十分に制限すると、制限されたサブセットを利用して効率的なコードにコンパイルできるオプティマイザーを構築できます。これは、実際には興味深い利点ではありません。実際、よく知られています。しかし、そもそもPython(またはRuby)を使用することの全体的なポイントは、おそらく十分に分析されず、優れたパフォーマンスをもたらす興味深い機能を使用したかったということです!興味深い質問は実際には...

さらに、PyPy / RubiniusなどのJITコンパイラーはC / C ++のパフォーマンスに匹敵しますか?

いや

つまり、コードの実行中に、十分なタイピング情報と、すべてのコードをマシンコードまでコンパイルするのに十分なホットスポットを取得できる可能性があります。そして、おそらく、これをいくつかのコードでCよりも優れたパフォーマンスを得ることができます。私はそれが非常に物議を醸すとは思わない。しかし、それでも「ウォームアップ」する必要があり、パフォーマンスは依然として予測可能性が少し低く、一貫して予測可能な高いパフォーマンスを必要とする特定のタスクではCやC ++ほど優れていません。

PythonやRubyよりも多くの型情報を持ち、PythonやRubyよりも優れたJITコンパイラーを持っているJavaの既存のパフォーマンスデータは、まだC / C ++に一致しません。ただし、同じ球場にあります。


1
「もちろん、トレードオフはPythonコードのサブセットのみが受け入れられるということです。むしろ、Pythonコードのサブセットのみがうまくいくことができます。つまり、解析して静的型を指定できるサブセットです。」これはあまり正確ではありません。RPythonコンパイラーで受け入れられるのは、サブセットのみです。Pythonのコンパイルが困難な部分はRPythonプログラムでは決して発生しないことが保証されているため、RPythonを合理的に効率的なCにコンパイルすることは正確に機能します。単に発生した場合に最適化されないというだけではありません。すべてのPythonを処理するのは、コンパイルされたインタープリターです。
ベン

1
JITについて:JavaがいくつかのフレーバーのC(++)を上回っている複数のベンチマークを見てきました。ブースト付きのC ++のみが確実に先行しているようです。その場合、開発者の時間あたりのパフォーマンスについて疑問に思いますが、それは別のトピックです。
ラファエル

@Ben:RPythonを作成したら、RPythonコンパイラーが失敗したときにCPythonインタープリターを使用するようにフォールバックするコンパイラー/インタープリターを作成するのは簡単です。したがって、「Pythonコードのサブセットのみがうまくいく:...」は完全に正確です。
ライライアン

9
@Raphael よく書かれた C ++コードがJavaよりも優れていることが何度も示されてます。しかし、「よく書かれた」部分がC ++で取得するのがかなり難しいので、多くのベンチでJavaがC ++よりも優れているという結果を目にします。したがって、C ++はより高価ですが、厳密なmemコントロールと金属グリットが必要な場合は、C / C ++が最適です。特にCはac / pアセンブラーです。
-TC1

7
言語仕様の一部としてアセンブリを直接インライン化できるため、他の言語の最大パフォーマンスをC / C ++などの言語と比較することは、無益さの一種です。マシンが任意の言語で書かれたプログラムを実行できることはすべて、実行された命令のトレースからアセンブリを作成することで、最悪でも複製できます。@Raphaelが以前のコメントで示唆したように、もっと興味深い測定基準は、開発努力あたりのパフォーマンス(工数、コード行など)です。
Patrick87

18

簡単な答えは:私たちは知りません、100年後に再び尋ねます。(それでもまだわからないかもしれません。おそらくわからないかもしれません。)

理論的には、これは可能です。これまでに作成されたすべてのプログラムを取得し、それらを可能な限り最も効率的なマシンコードに手動で変換し、ソースコードをマシンコードにマッピングするインタープリターを作成します。これが可能なのは、限られた数のプログラムしか書かれていないためです(さらに多くのプログラムが書かれるにつれて、手動翻訳を続けてください)。もちろん、これは実際的には完全にばかげています。

理論的には、高レベル言語はマシンコードのパフォーマンスに到達できる可能性がありますが、それを超えることはありません。実際には、マシンコードの作成に頼ることはほとんどないため、これは依然として非常に理論的です。この引数は、高レベル言語の比較には適用されません。CがPythonよりも効率的である必要があることを意味するものではありません。

反対側から来る、純粋に実験的な用語では、ほとんどの場合、解釈された高レベル言語はコンパイルされた低レベル言語よりもパフォーマンスが悪いことがわかります。私たちは、CやPythonのような言語が間に挟まれた、非常に高レベルの言語とアセンブリのタイムクリティカルな内部ループで、時間に依存しないコードを書く傾向があります。これをバックアップする統計情報はありませんが、ほとんどの場合、これが実際に最善の決定だと思います。

ただし、高レベルの言語が現実的に書くコードを打ち負かすような競合しない例があります:特殊なプログラミング環境。多くの場合、MatlabやMathematicaなどのプログラムは、単なる人間が書くことができるものよりも、特定の種類の数学的問題を解決するのにはるかに優れています。ライブラリ関数はCまたはC ++(「低レベル言語がより効率的」なキャンプへの燃料です)で記述されている可能性がありますが、Mathematicaコードを記述している場合、それは私のビジネスではありません。ライブラリはブラックボックスです。

理論的には、PythonがCよりも最適なパフォーマンスに近い、あるいはさらに近い可能性がありますか?上で見たように、はい、しかし、私たちは今日からそれとはほど遠いです。繰り返しになりますが、コンパイラーは過去数十年で多くの進歩を遂げており、その進歩は減速していません。

高水準言語は、より多くのものを自動化する傾向があるため、実行する作業が多くなり、効率が低下する傾向があります。一方、セマンティック情報が多くなる傾向があるため、最適化を見つけやすくなります(Haskellコンパイラを記述している場合、別のスレッドが鼻の下の変数を変更することを心配する必要はありません)。リンゴとオレンジの異なるプログラミング言語を比較するためのいくつかの取り組みの1つは、コンピューター言語ベンチマークゲーム(以前のシュートアウト)です。Fortranは、数値タスクで光りがちです。しかし、構造化データまたは高速スレッド交換の操作に関しては、F#、Scalaはうまく機能します。これらの結果を福音として受け取らないでください。彼らが測定していることの多くは、各言語のテストプログラムの作成者がどれだけ優れていたかです。

高水準言語を支持する主張は、現代のシステムでのパフォーマンスは実行される命令の数とそれほど強く相関せず、時間とともにそれほど相関しないということです。低レベル言語は、単純なシーケンシャルマシンに適しています。高水準言語が2倍の命令を実行するが、キャッシュをよりインテリジェントに使用してキャッシュミスを半分にする場合、最終的に勝者になる可能性があります。

サーバーおよびデスクトッププラットフォームでは、CPUはほとんど高速にならないプラトーに近づいています(モバイルプラットフォームも近づいています)。これは、並列処理の活用が容易な言語に有利です。多くのプロセッサは、ほとんどの時間をI / O応答の待機に費やしています。計算に費やされる時間は、I / Oの量と比較してほとんど重要ではなく、プログラマーが通信を最小限に抑えることを可能にする言語が有利です。

全体として、高水準言語はペナルティから始まりますが、改善の余地があります。彼らはどれくらい近づくことができますか?100年後にもう一度質問してください。

最後の注意:多くの場合、比較は、言語A で記述できる最も効率的なプログラムと言語Bで同じではなく、各言語で記述された最も効率的なプログラムとの間でなく、記述可能な最も効率的なプログラム各言語で一定の時間内に人間によって。これにより、原理的にも数学的に分析できない要素が導入されます。実際には、これは多くの場合、最高のパフォーマンスは、パフォーマンス目標を達成するために記述する必要がある低レベルコードと、リリース日を満たすために書く時間がある低レベルコードとの妥協点であることを意味します。


高レベルと低レベルの区別は間違っていると思います。C ++は(非常に)高レベルにすることができます。しかし、最新の高レベルC ++は、低レベルのC ++よりも(必ずしも)パフォーマンスが低下しません。まったく逆です。C ++とそのライブラリは、パフォーマンスを低下させることなく高レベルの抽象化を提供するように慎重に設計されました。Haskellの例でも同じこと言えます。高レベルの抽象化により、最適化が妨げられるのではなく、しばしば可能になります。動的言語と静的言語の元々の区別は、この点でより理にかなっています。
コンラッドルドルフ

@KonradRudolphその通りです。低レベル/高レベルはいくぶんarbitrary意的な区別です。ただし、動的言語と静的言語の両方がすべてをキャプチャするわけではありません。JITは違いの多くを解消できます。基本的に、この質問に対する既知の理論的答えは些細で役に立たず、実用的な答えは「それは依存します」です。
ジル 'SO-悪であるのをやめる

それでは、質問は「JITがどれだけ上手くなり、静的コンパイルを追い越せば、静的にコンパイルされた言語も利益を得ることができるのだろうか」と思うのです。そして、はい、私はあなたの評価に同意しますが、確かに「依存する」以上の情報に基づいた推測を得ることができます。;-)
コンラッドルドルフ

@KonradRudolph推測が必要な場合は、Software Engineeringに問い合わせてください。
ジル 'SO-悪

1
残念ながら、言語の銃撃戦は量的ベンチマークの疑わしいソースです。言語の典型とみなされるプログラムだけをすべてのプログラムで受け入れるわけではありません。これは、トリッキーで非常に主観的な要件です。つまり、シュートアウトの実装が実際に良いとは想定できないことを意味します(実際、いくつかの実装では明らかに優れた代替が拒否されています)。裏返して; これらはマイクロベンチマークであり、一部の実装では、パフォーマンスを向上させるためだけに、通常のシナリオでは決して考えない珍しい手法を使用しています。だから、それはゲームであり、非常に信頼できるデータソースではありません。
イーモンネルボンヌ

10

C ++の文の基本的な違いx = a + bやPythonの文は、x = a + bC / C ++コンパイラが(と、それはの種類について容易に利用可能であることを少し余分な情報、この文から伝えることができるということであるxab実行する必要があり、正確にどのようなマシンコード) 。Pythonステートメントがどのような操作を行うのかを知るには、停止問題を解決する必要があります。

In C that statement will basically compile to one of a few types of machine addition (and the C compiler knows which one). In C++ it might compile that way, or it might compile to calling a statically known function, or (worst case) it might have to compile to a virtual method lookup and call, but even this has a fairly small machine code overhead. More importantly though, the C++ compiler can tell from the statically known types involved whether it can emit a single fast addition operation or whether it needs to use one of the slower options.

Pythonでは、コンパイラはそれを知っab両方とも理論的にはほぼ同じくらい良いことをすることができintます。追加のボクシングオーバーヘッドがいくつかありますが、型が静的に知られている場合は、おそらくそれも取り除くことができます(整数はメソッド、スーパークラスの階層などを持つオブジェクトであることをインターフェイスに示します)。問題はPythonのコンパイラができないことですこれは、クラスが実行時に定義され、実行時に変更できるため、定義およびインポートを行うモジュールでさえ実行時に解決されるためです(実行されるインポートステートメントでさえ、実行時にのみ認識できるものに依存します)。そのため、Pythonコンパイラーは、コンパイルしているステートメントが何をするのかを知るために、どのコードが実行されたかを知る必要があります(つまり、停止問題を解決します)。

そのため、理論的に可能な最も洗練された分析を行ったとしても、特定のPythonステートメントが事前に何を行うかについて多くを語ることはできません。これは、洗練されたPythonコンパイラが実装されたとしても、ほとんどすべての場合、Python辞書ルックアッププロトコルに従うマシンコードを発行して、オブジェクトのクラスを決定し、メソッドを見つける必要があることを意味します(クラス階層のMROをトラバースし、実行時に動的に変更することもできるため、単純な仮想メソッドテーブルにコンパイルするのは困難です)、基本的に(遅い)インタープリターが行うことを行います。これが、動的言語用の洗練された最適化コンパイラが実際に存在しない理由です。作成するのは難しいだけではありません。最大の見返りはありません」

これは、コードが何に基づいていないことに注意されたこと、それはコードが何に基づいている可能性がやっています。単純な一連の整数算術演算であるPythonコードでさえ、任意のクラス演算を呼び出しているかのようにコンパイルする必要があります。静的言語は、コードが実行できることについてより大きな制限を持っているため、コンパイラーはより多くの仮定を行うことができます。

JITコンパイラーは、実行時までコンパイル/最適化を待つことでこれを獲得します。これにより、コード実行できることではなく、コード実行していることに対して機能するコードを発行できます。このため、JITコンパイラーは、静的言語よりも動的言語の方がはるかに大きな見返りがあります。より静的な言語の場合、オプティマイザーが知りたいことの多くは事前に知ることができるので、それを最適化して、JITコンパイラーが行うことを少なくすることができます。

動的言語用のさまざまなJITコンパイラがあり、コンパイルおよび最適化されたC / C ++に匹敵する実行速度を達成すると主張しています。JITコンパイラーによる最適化でさえ、どの言語の事前コンパイラーでも実行できないため、理論的には(一部のプログラムの)JITコンパイルは、いつか最高の静的コンパイラーを上回る可能性があります。しかし、デヴィンが正しく指摘したように、JITコンパイルのプロパティ(「ホットスポット」のみが高速で、ウォームアップ期間後のみ)は、JITでコンパイルされた動的言語が、すべての可能なアプリケーションに適している可能性が低いことを意味します通常、静的にコンパイルされた言語と同じかそれより高速です。


1
これは、コメントなしの2つの下票です。この答えを改善する方法についての提案を歓迎します!
ベン

私は投票しませんでしたが、あなたは「停止問題を解決する必要がある」ことについて間違っています。多くの状況で、動的言語のコードを最適なターゲットコードにコンパイルできることが実証されていますが、私の知る限り、これらのデモには停止問題の解決策は含まれていません:
mikera

@mikera申し訳ありませんが、いいえ、あなたは間違っています。完全に一般的な Pythonまたは他の動的言語用のコンパイラ(GCCはコンパイラであると理解しているという意味で)を実装した人はいません。このようなシステムはすべて、言語のサブセット、または特定のプログラムのみで機能し、基本的にハードコーディングされたプログラムを含むインタープリターであるコードを出力することもあります。必要にfoo = x + y応じて、コンパイル時の加算演算子の動作を予測することが停止問題の解決に依存する行を含むPythonプログラムを作成します。
ベン

私は正しいです、そして、あなたはポイントを逃していると思います。「多くの状況で」と言いました。「あらゆる状況で」とは言いませんでした。停止の問題に関連する人為的な例を構築できるかどうかは、現実の世界ではほとんど無関係です。FWIW、C ++の同様の例を構築することもできますので、とにかく何も証明しません。とにかく、私はあなたの答えの改善を提案するためだけに、議論に入るためにここに来ませんでした。それを取るか、それを残す。
-mikera

@mikeaあなたはその点を見逃しているかもしれません。x + y効率的なマシンの追加操作にコンパイルするためには、コンパイル時にそうであるかどうかを知る必要があります。ただの時間ではなく、すべての時間。動的言語の場合、合理的なヒューリスティックがほとんどの場合正しいと推測しますが、現実的なプログラムではこれはほとんど不可能です。コンパイルにはコンパイル時の保証が必要です。したがって、「多くの状況で」について話すことによって、あなたは実際に私の答えにまったく取り組んでいないことになります。
ベン

9

動的言語の最悪のシナリオの概要を示すクイックポインター:

Perlの解析は計算できません

その結果、(完全な)Perlを静的にコンパイルすることはできません。


一般的に、いつものように、それは依存します。静的にコンパイルされた言語で動的な機能をエミュレートしようとすると、よく考えられたインタープリターまたは(部分的に)コンパイルされたバリアントが静的にコンパイルされた言語のパフォーマンスに近づくかアンダーカットする可能性があると確信しています。

覚えておくべきもう1つのポイントは、動的言語はC以外の別の問題を解決するということです。多くの場合、実行時のパフォーマンスは主要な関心事ではありません。たとえば、市場投入までの時間は、開発者が短時間で複雑で高品質のシステムを作成できるかどうかに依存します。人気のあるもう1つの機能は、プラグインなどの再コンパイルなしの拡張性です。これらの場合、どの言語を好みますか?


5

この質問に対してより客観的に科学的な答えを提供しようとして、私は次のように主張します。動的言語では、実行時に決定を下すためにインタープリターまたはランタイムが必要です。このインタープリター、またはランタイムはコンピュータープログラムであり、静的または動的のプログラミング言語で作成されています。

インタープリター/ランタイムが静的言語で記述されている場合、(a)解釈する動的プログラムと同じ機能を実行し、(b)少なくとも同様に実行するプログラムを静的言語で作成できます。これらの主張の厳密な証拠を提供するには、追加の(おそらくかなりの)努力が必要になるため、これが自明であることを願っています。

これらの主張が真実であると仮定すると、唯一の解決策は、インタプリタ/ランタイムも動的言語で書かれることを要求することです。ただし、以前と同じ問題に遭遇します。インタープリターが動的である場合、インタープリター/ランタイムが必要であり、これもプログラミング言語(動的または静的)で作成されている必要があります。

インタプリタのインスタンスが実行時にそれ自体を解釈できると仮定しない限り(これが自明であることは自明であることを願っています)、静的言語を破る唯一の方法は、各インタプリタインスタンスが個別のインタプリタインスタンスによって解釈されることです。これは、無限回帰(これが自明であることを願っています)または通訳者の閉じたループ(これも自明であることを願っています)につながります。

理論上でも、一般に、動的言語は静的言語よりも優れたパフォーマンスを発揮できないようです。現実的なコンピューターのモデルを使用する場合、さらにもっともらしいようです。結局、マシンはマシン命令のシーケンスのみを実行でき、マシン命令のすべてのシーケンスは静的にコンパイルできます。

実際には、動的言語のパフォーマンスを静的言語と一致させるには、静的言語でインタープリター/ランタイムを再実装する必要があります。ただし、それができるのは、この議論の要点です。それは鶏と卵の質問であり、上記の未定の(私の意見では、ほとんど自明である)仮定に同意すれば、実際に答えることができます。動的な言語ではなく静的な言語にうなずく必要があります。

この議論に照らして、質問に答える別の方法は次のとおりです。現代のコンピューティングの中心にあるストアドプログラム、control = dataコンピューティングモデルでは、静的コンパイルと動的コンパイルの区別は誤った二分法です。静的にコンパイルされた言語には、実行時に任意のコードを生成および実行する手段が必要です。それは基本的に普遍的な計算に関連しています。


これを読み直して、私はそれが本当だとは思わない。JITコンパイルはあなたの議論を壊します。たとえば、最も単純なコードでさえ、値がわかれば、大幅に高速化main(args) { for ( i=0; i<1000000; i++ ) { if ( args[0] == "1" ) {...} else {...} }できますargs(それが変化しないと仮定すると、アサートできる可能性があります)。静的コンパイラーは、比較をドロップするコードを作成できません。(もちろん、この例ifでは、ループから抜け出すだけです。しかし、もっと複雑になるかもしれません。)
ラファエル

@Raphael私は、JITはおそらくだと思います私の議論を。JITコンパイルを行うプログラム(JVMなど)は通常、静的にコンパイルされたプログラムです。静的にコンパイルされたJITプログラムが他の静的プログラムが同じ作業を行うよりも速くスクリプトを実行できる場合、JITコンパイラーでスクリプトを「バンドル」し、バンドルを静的にコンパイルされたプログラムと呼びます。これは、少なくとも別個の動的プログラムで動作するJITと同じように実行する必要があり、JITがより良くしなければならないという議論に矛盾します。
Patrick87

ええと、それはRubyスクリプトをそのインタプリタにバンドルすると静的にコンパイルされたプログラムが得られると言っているようなものです。私は同意しません(この点で言語のすべての区別を削除するため)が、それは概念ではなく意味論の問題です。概念的には、実行時にプログラムを適応させる(JIT)ことで、コンパイル時にはできない最適化(静的コンパイラー)を行うことができ、それが私のポイントです。
ラファエル

@Raphael意味のある区別がないというのは答えの種です:一部の言語を静的として厳格に分類しようとする試みは、パフォーマンスの制限に苦しんでいますが、まさにこの理由で失敗します:事前にパッケージ化された(Ruby 、スクリプト)バンドルとCプログラム。(Ruby、スクリプト)がマシンに命令の正しいシーケンスを実行させ、特定の問題を効率的に解決できる場合、巧妙に作成されたCプログラムも可能です。
Patrick87

ただし、違い定義できます。1つのバリアントは、手元のコードを変更せずにプロセッサに送信し(C)、もう1つのバリアントは実行時にコンパイルします(Ruby、Java、...)。1つ目は「静的コンパイル」の意味で、2つ目は「ジャストインタイムコンパイル」(データ依存の最適化が可能)です。
ラファエル

4

Rubyのような動的言語用のコンパイラをビルドして、C / C ++と同等のパフォーマンスを比較できますか?

答えは「はい」だと思います。私はまた、効率の点で(たとえ多少でも)現在のC / C ++アーキテクチャを超えることさえできると信じています。

理由は簡単です。コンパイル時よりもランタイムに多くの情報があります。

動的型はわずかな障害にすぎません。関数が常にまたはほぼ常に同じ引数型で実行される場合、JITオプティマイザーはその特定の場合に分岐およびマシンコードを生成できます。そして、できることは他にもたくさんあります。

GoogleのSteve YeggeによるスピーチであるDynamic Languages Strike Backを参照してください(私が信じているところにはビデオ版もあります)。彼は、V8からの具体的なJIT最適化手法に言及しています。感動!

私たちは今後5年間で私たちが持っているものを楽しみにしています!


2
私は楽観主義が大好きです。
デイブクラーク

スティーブの講演には、不正確さに対する非常に具体的な批判がいくつかあったと思います。見つけたら投稿します。
コンラッドルドルフ

1
@DaveClarkeそれは私が走り続けることです:)
コス

2

これが理論的に可能であると思われる人々、または遠い将来、私の意見では完全に間違っています。ポイントは、動的言語がまったく異なるプログラミングスタイルを提供し、課すという事実にあります。実際、両方の側面が相互に関連していても、違いは2つあります。

  • シンボル(vars、またはすべての種類のid <-> datumバインディング)は型付けされていません。
  • 構造(データ、実行時に存在するすべてのもの)も、要素のタイプによって型指定されません。

2番目のポイントは、汎用性を無料で提供します。ここでの構造は、複合要素、コレクションだけでなく、それ自体の型であり、すべての種類(関数、アクション、操作)の(!)ルーチンでさえあることに注意してください...とにかく実行時にチェックが行われます。私たちは、記号を入力した可能性があり、まだ(配列は、その要素の種類に応じて型指定されていないものを構造化しているaだけではないintの配列として配列として入力されます)が、この数は(動的言語では真でなくてもa同様に含むことができ、文字列)。

L

  • Elementを含む、完全にポリモーフィックな(C)型LL
  • すべてのシンボルはそのタイプでありElement、任意の要素を保持できますL
  • すべての構造(ここでも、モデルルーチンを含む)はElementのみを受け取ります

これはパフォーマンスの大きなペナルティに過ぎないことは明らかです。また、他の投稿で詳しく説明されているすべての結果(プログラムの感度を確保するために必要なあらゆる種類のランタイムチェックの無数)にも触れていません。


+1非常に興味深い。私の答えを読みましたか?あなたと私の考えは似ているように見えますが、あなたの答えはより詳細で興味深い視点を提供します。
Patrick87

動的言語を型指定する必要はありません。Cで動的言語のモデルを実装すると、最適化の可能性が大幅に制限されます。コンパイラが高レベルの最適化(不変データなど)を認識することを非常に難しくし、いくつかの基本的な操作(関数呼び出しなど)を強制的にCに通します。事前評価済み。ネイティブコンパイラのパフォーマンスは大幅に向上する傾向がありますが、バイトコードのデコードによって違いを正当化できるとは思えません。
ジル 'SO-悪であるのをやめる

@ Patrick87:あなたは正しい、私たちの考え方は非常に似ているようだ(以前読んだことがなかった、申し訳ありませんが、私の反射は現在Cでdyn langを実装することから来ています)。
-18:

@Gilles:静的に型付けされていないという意味であれば、「...型付けを解除する必要はない」ことに同意します。しかし、これは一般的にダインラングスについて人々が考えるものではありません。私は個人的に、汎用性(上記の回答で与えられた一般的な意味で)をはるかに強力でなくてはならない機能と考えています。特定の(一見多形的な)型を定義する方法について考えを広げたり、インスタンスに直接柔軟性を与えたりすることで、静的型の処理方法を簡単に見つけることができます。
SPIR

1

私はすべての答えを詳細に読む時間がありませんでした...しかし、私は面白がっていました。

1960年代から70年代前半にも同様の論争がありました(コンピューターサイエンスの歴史はしばしば繰り返されます):高レベル言語をコンパイルして、プログラマーが手作業で作成したマシンコード(アセンブリコードなど)と同じくらい効率的なコードを作成できますか?誰もがプログラマーがどのプログラムよりもはるかに賢く、非常に賢い最適化を考え出すことができることを知っています(実際には、現在はピープホール最適化と呼ばれるものをほとんど考えています)。これはもちろん私の皮肉なことです。

コード拡張の概念さえありました:優れたプログラマーによって生成された同じプログラムのコードのサイズに対するコンパイラーによって生成されたコードのサイズの比率(これらが多すぎるように:-)。もちろん、この比率は常に1より大きいという考えでした。当時の言語は、CobolとFortran 4、または知識人向けのAlgol 60でした。Lispは考慮されていなかったと思う。

誰かがコンパイラを作成して、時々1の拡張率を得ることができるという噂がありました...コンパイルされたコードが手書きのコードよりもはるかに優れている(そしてより信頼できる)ルールになるまで。当時の人々はコードサイズを心配していました(小さな記憶)が、速度やエネルギー消費についても同じことが言えます。理由は説明しません。

奇妙な機能、言語の動的な機能は重要ではありません。重要なのは、それらが使用されるかどうか、使用されるかどうかです。パフォーマンスは、どんな単位(コードサイズ、速度、エネルギーなど)でも、プログラムの非常に小さな部分に依存することがよくあります。したがって、表現力を与える施設が実際に邪魔にならない可能性が十分にあります。優れたプログラミングの実践では、高度な機能は規律ある方法でのみ使用され、新しい構造を想像します(これがLispのレッスンでした)。

言語に静的な型付けがないという事実は、その言語で書かれたプログラムが静的に型付けされないことを意味していません。一方、プログラムが使用する型システムは、型チェッカーが現在存在するにはまだ十分に形式化されていない可能性があります。

議論の中で、最悪の場合の分析(「停止問題」、PERL解析)への参照がいくつかありました。しかし、最悪の場合の分析はほとんど無関係です。重要なのは、ほとんどの場合または有用な場合に何が起こるかです...しかし、定義されているか、理解されているか、経験されています。プログラムの最適化に直接関係する別の話があります。ずっと前にテキサスの主要な大学で博士課程の学生と彼の顧問(後に国立アカデミーの1人に選出された)の間で行われました。私が思い出すように、学生はアドバイザーが手に負えないことを示した分析/最適化問題の研究に固執していた。すぐに彼らは言葉を話すことはもうありませんでした。しかし、学生は正しかった。ほとんどの実際のケースでは問題は十分に扱いやすいので、彼が作成した論文は参考研究となった。

そしてPerl parsing is not computable、その文が意味するものは何でも、文言についてさらにコメントするために、非常によく形式化された言語であるMLにも同様の問題があります。Type checking complexity in ML is a double exponential in the lenght of the program.それは最悪のケースの複雑さをもたらす非常に正確で正式な結果です...それはまったく問題ではありません。Afaik、MLユーザーは、型チェッカーを爆発させる実用的なプログラムをまだ待っています。

多くの場合、以前と同様に、人間の時間と能力は計算能力よりも不足しています。

将来の本当の問題は、まだ使用されているすべてのレガシーソフトウェアを書き換える必要なく、新しい知識、新しいプログラミング形式を統合するために言語を進化させることです。

数学を見ると、それは非常に大きな知識体系です。それを表現するために使用される言語、表記法、概念は何世紀にもわたって進化してきました。新しい概念で古い定理を書くのは簡単です。私たちは主な証明を適応しますが、多くの結果を気にしません。

しかし、プログラミングの場合、すべての証明を最初から書き直さなければならない場合があります(プログラムは証明です)。私たちが本当に必要とするのは、非常に高レベルで進化可能なプログラミング言語であるかもしれません。オプティマイザーの設計者は喜んで従います。


0

いくつかのメモ:

  • すべての高水準言語が動的であるとは限りません。Haskellは非常に高レベルですが、完全に静的に型付けされています。Rust、Nim、Dなどのシステムプログラミング言語でも、高レベルの抽象化を簡潔かつ効率的に表現できます。実際、動的言語と同じくらい簡潔にすることができます。

  • 動的言語用の高度に最適化された事前コンパイラが存在します。適切なLisp実装は、同等のCの半分の速度に達します。

  • ここでは、JITコンパイル大きな勝利になる可能性があります。CloudFlareのWebアプリケーションファイアウォールは、LuaJITによって実行されるLuaコードを生成します。LuaJITは、実際に実行される実行パス(通常、非攻撃パス)を大幅に最適化するため、実際のワークロードで静的コンパイラーによって生成されるコードよりもコードがはるかに高速に実行されます。プロファイルに基づく最適化を備えた静的コンパイラとは異なり、LuaJITは実行時の実行パスの変更に適応します。

  • 最適化の解除も重要です。monkeypatchedされているクラスをチェックする必要のあるJITコンパイルされたコードの代わりに、monkeypatchingの動作はランタイムシステムでフックをトリガーし、古い定義に依存していたマシンコードを破棄します。


これはどう答えますか?参照を追加した場合は、おそらく3番目の項目です。
ラファエル

PGOは、通常のワークロードの下でのWebアプリケーションコードのLuaJITのパフォーマンスに匹敵することができないという主張には非常に懐疑的です。
コンラッドルドルフ

@KonradRudolph JITの主な利点は、異なるパスがホットになるとJITがコードを適応させることです。
デミ

@Demetriこれは知っています。しかし、これが利点であるかどうかを定量化するのは非常に困難です。私の答えとコメントのディスカッションを参照してください。簡単に言うと、JITは使用状況の変化に適応できますが、実行時に追跡する必要もあり、オーバーヘッドが発生します。この損益分岐点は、動作の頻繁な変更が発生する場合にのみ直感的に使用できます。Webアプリの場合、おそらく最適化が報われる単一の(またはごく少数の)使用パターンがあるため、適応性による最小限のパフォーマンスの向上は、継続的なプロファイリングのオーバーヘッドを相殺しません。
コンラッドルドルフ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.