web-dev-qa-db-ja.com

ASTインタプリタを改善するために取るべき方向

特定のテーマについての意見やアドバイスを探しています。状況は次のとおりです。私は暇なときに、C#で作成されたプログラミング言語インタープリターを楽しみのために開発しており、その速度を向上させたいと考えています。レクサー(うまく機能します!)、パーサー(うまく機能します!)、オプティマイザー(うまく機能します!)、インタープリター(うまく機能しますが、はるかに優れている可能性があります)を作成しました。

BASICとVB.Netを組み合わせた小さな構文を作成しました。それはこのように見えます:

FUNCTION Main(args[])
    # The language actually supports .Net API.
    System.Console.WriteLine(FirstMethod(100).ToString())
END FUNCTION

FUNCTION FirstMethod(num)
    IF num > 1 THEN
        RETURN FirstMethod(num - 1)
    END IF
    RETURN num
END FUNCTION

コンポーネント

  1. レクサーはテキストをトークンのセットに変換します。
  2. パーサーはそれをSystem.CodeDom構造に似たAST(抽象構文木))に変換します。
  3. インタプリタがアルゴリズムを再帰的に解釈するため、上記のコードを使用すると、StackOverflowExceptionにすばやく到達します。したがって、オプティマイザーの役割は、主にメソッド呼び出し、ループ、条件などをインライン化することです。これは、ラベル、GoTo、および条件のセットに置き換えられます。目標は、最後に1つのブロックのみを持つメソッド本体を持つことです。それもかなりうまくいきます。引数に1000000を指定して上記のコードを実行しても、StackOverflowが取得されません。
  4. 次に、インタプリタはASTを参照し、すべてを解釈します。このコンポーネントアーキテクチャは、数年前に作成したこのPoCに似ています。 https://github.com/veler/AlgorithmSimulator ただし、いくつかの点で、特に2項演算子を実行する方法が大きく異なることに注意してください。

通訳に関する要点

  1. これは.Netで作成されており、私の言語はオブジェクトのプロパティと.Netメソッドへのアクセスをサポートしています。つまり、基本的に "RETURN num.ToString()"と書くことができますが、Reflectionのおかげで機能します。プロパティとメソッドは、最初に呼び出されたときにデリゲートキャッシュに保存されるため、2回目の呼び出しが高速になります。
  2. 私の新しいバージョンでは、二項演算子はdynamicキーワードを使用して実装されています。パフォーマンスには最適ではありません。しかし、この時点ではメンテナンスに適していて、読みやすいです。私はそれが間違いなく最も最適化されたソリューションではないことを認識しています。

公演

したがって、パフォーマンスについて話すと、上記のアルゴリズムは、GitHubでの2015年の以前の実装で、Inteli5で実行するのに約8ミリ秒かかります。プロパティ/メソッドへのアクセスをキャッシュし、Dynamicキーワードを使用する私の新しい実装は、実行に約5ミリ秒かかります(解析と最適化の時間をカウントしません)。

オプティマイザーを使用してすべてをインライン化すると、速度が平均10%向上します(もちろん、アルゴリズムによって異なります)。

いいですが、それでも遅いハハ

作成するのが楽しいPoCができたので、もっと速くしたいと思います。特にインタプリタ(他のコンポーネントは「OK」です)。私が最初にしたことは、VisualStudioのPerformanceProfilerを確認することですが、他のいくつかのプロジェクト(プロまたはパーソナル)と比較すると、関連するものは何もありません(Reflectionは、プログラムの他の部分と比較してそれほど遅くはありません。二項演算子を実行しないと、まだ遅いです...スイッチオンタイプはたくさんありますが、これから逃れるためのクリーンな方法が見つかりませんでした...など)。すべてがかなり速く進みますが、グローバルな結果は遅いです。だから実際にはすべてが遅いです...

私はいくつかのアイデアを念頭に置いており、それはほとんどのインタープリターを書き直す必要があります(しかし、私はそれで大丈夫です、それは何の後の楽しみのためのプロジェクトです)、そしてそれについてあなたの意見をお願いします。

  1. IL.Emitの使用:よく理解していれば、実行時にCIL/MSILを直接注入します。小さな式(二項演算子のように...これはこの特定の部分の次の実装になる可能性があります)には最適に見えますが、AST全体に実装するのは難しいようです。
  2. Linq.Expressionsのコンパイル:ユーザーがのような構文を使用すると、コンストラクターを自動的に解決し、クラスをインスタンス化するためにすでに使用しています。 "NEW Namespace.Class(Foo、Bar)"。これは魅力のように機能し、デリゲートのおかげでコンストラクターをキャッシュに保持するため、完全なReflectionプロセスよりもパフォーマンスが向上します。 Reference SourceのWebサイトで見たところ、IL.Emitを使用しています。しかし、それでもAST全体で機能するでしょうか?不思議なんだけど。
  3. ライブラリ "CSScript"( http://csscript.net/ )が "スクリプト内のすべてのステートメントを、ステートメント全体で頻繁に使用されている場合でも、一度だけ解釈することを確認しました。コード"。この黒魔術がどのように機能するかについての技術的な説明はありますか?

最後のポイント:数年前の私の最初のPoCには、アルゴリズムを段階的に実行し、アルゴリズムにブレークポイントを設定し、すべての変数の値を使用して呼び出しスタックを確認する独自のシステムがありました(実装するのは楽しかったです) !)。これは私の最後のPoCには実装されておらず、これら2つの最初のアイデアのいずれかを使用することで再び実行できるかどうかはわかりません。しかし、私はそれで大丈夫です。それは副次的な目標です。

私はどんな意見や他のアイデアにもオープンです。 =)それについて話し合いましょう?

私のためにすべてを行うライブラリを使用したくないことに注意してください( http://csscript.net/ またはRoslynとその友人)。私の目標は、やりがいのあるプロジェクトを楽しんで学ぶことです。また、C++で書き直したくありません。この.Netプロジェクトをより速くする方法があると私は確信しています。

1
Etienne Baudoux

ASTから操作への変換は実行時ではなくコンパイル時に行われるため、実際のバイトコードを出力すると、(この種のコードの場合)常にこれらのオプションの最高のパフォーマンスが得られます。実行時情報を利用してさらに最適化を実行する可能性はほとんどありません。

そして正直なところ、バイトコードはASTにより近くマッピングされるはずなので、少しの学習曲線を乗り越えれば、バイトコードの発行も簡単になります。

1
Telastyn