2

2つの操作モードをサポートするルールエンジンがあります。

  1. C#プログラムへのコンパイルとエンジンへのリンク
  2. 逆ポーランドスタックベースの命令に解析し、解釈します

ルールは、関数呼び出し(max、min、sin、cosなど)を使用した単純な数式です。

コンパイルされたバージョン(つまり#1)は、解釈されたバージョン(つまり、#2)よりもはるかに高速であると想定していました。実際、これが、そもそもコンパイル済みモードを使用する主な理由です。しかし、私の速度テストはそうではないことを示しました。

コンパイル済みバージョン

Action<double>[] Rules = new[] { calc1, calc2, calc3 ... };
double[] v = new double[...];   // Variables

void calc1(double arg) { v[3]=v[12]+v[15]/v[20] };   // "x3=x12+x15/x20"
void calc2(double arg) { ... };
   :
// Start timer now
Rules.AsParallel().ForAll(r => r(...));
// End timer

通訳版

Expression[] Rules = ...
// Each rule is already parsed into an Expression object, which is a set of
// reverse-polish stack-based instructions.
// For example, "x3=x12+x15/x20" will be parsed to:
//     [ Push(12), Push(15), Push(20), Divide(), Add() ]
// Start timer now
Rules.AsParallel().ForAll(r => r.Evaluate(...));
// End timer

ここで、「Expression」はサードパーティのライブラリの一部であり、単純な文字列を逆ポーランドスタックベースの命令の単純なセットに解析し、それを解釈することができます。明確にするためだけに、LINQの式ツリーオブジェクトではありません。

注:実際のコードでは、ルールを「レイヤー」で並べ替えてレイヤーを順番に計算するため、同時実行性について心配する必要はありません。各レイヤーは、前のレイヤーで計算された値にのみ依存します。どちらのモードもまったく同じレイヤー構造になっています。

結果

驚いたことに、解釈されたバージョンはコンパイルされたバージョンよりもはるかに高速に実行され、平均して4倍になります。つまり、コンパイルされたバージョンは約1,200のルールを実行するのに0.3秒かかりましたが、解釈されたバージョンは平均0.08〜0.1秒かかりました。

私のコンピューターはまあまあのデュアルコアCore2です。

.NET 4.0、VisualStudio10を使用しています。

パフォーマンスは、デバッグビルドとリリースビルドで同様です。

私の質問

コンパイルモードで大幅な速度低下を引き起こしている可能性があるのは何ですか?

注:1つの可能な回答を投稿しました

4

1 に答える 1

1

.NET は JIT コンパイル環境であるため、JIT コンパイルするコードが増えるほど、速度が低下します。1,200 のメソッドが実行時に JIT コンパイルされる可能性がありますが、インタープリター モードでは、インタープリターのみが 1 回 JIT コンパイルされます。コンパイル済みモードのループで余分な JIT 時間が発生することがあります。

実験:

  1. 各モードを 5 回実行します (JIT コンパイルを完了してキャッシュを埋めるためだけに)
  2. 時間 50 ラン、平均を取る

結果:

  1. コンパイル済みモード: 実行あたり 1.6 ミリ秒
  2. 解釈されたモード: 実行あたり 5.3 ミリ秒

所見:

コンパイル済みモードの最初の実行に、かなりの時間が費やされているようです。

コンパイル済みモードの2回目の実行は、すでに解釈済みモードと同様の速度になっています。

解釈されたモードは、実行の数によって大幅に高速化されません。

したがって、ルール コードは最初の実行時に JIT コンパイルされるという私の見解を示唆しています。

于 2011-05-11T12:16:53.920 に答える