编译表达式的运行速度比解释版本慢得多

发布于 2024-11-06 02:57:18 字数 1442 浏览 3 评论 0原文

我有一个规则引擎,它支持两种操作模式:

  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 倍!换句话说,编译版本需要 0.3 秒才能运行完大约 1,200 条规则,而解释版本平均需要 0.08-0.1 秒。

我的电脑是一般的双核Core2。

我正在使用 .NET 4.0、Visual Studio 10。

调试或发布版本中的性能相似。

我的问题

是什么导致编译模式显着减慢?

注意:我已经发布了一个可能的答案

I have a rules engine, which supports two modes of operations:

  1. Compilation into a C# program and linked into the engine
  2. Parsing into a reverse-polish stack-based instructions and interpreted

The rules are simple arithmetical expressions with function calls (max, min, sin, cos etc.)

I would have assumed the compiled version (i.e. #1) to be much faster than the interpreted version (i.e. #2) -- in fact, that's the main reason for having a compiled mode in the first place. However, my speed tests showed otherwise.

Compiled Version

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

Interpreted Version

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

Here, "Expression" is part of a third-party library that parses a simple string into a simple set of reverse-polish stack-based instructions, which can then be interpreted. It is not the expression-tree object in LINQ -- just for clarification.

Note: don't worry about concurrency since in the real code, I sort the rules by "layers" and calculate the layers sequentially, which each layer only depending on values calculated in previous layers. Both modes have the exact same layers structure.

The Results

Shockingly, the interpreted version runs MUCH faster than the compiled version, average a factor of 4x! In other words, the compiled version took 0.3s to run through around 1,200 rules, while the interpreted version took on average 0.08-0.1s.

My computer is a so-so dual-core Core2.

I am using .NET 4.0, Visual Studio 10.

Performance is similar in Debug or Release builds.

My Question

What can be causing the significant slowdown in the compiled mode?

NOTE: I HAVE POSTED ONE POSSIBLE ANSWER

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

書生途 2024-11-13 02:57:18

.NET 是一个 JIT 编译环境,因此 JIT 编译的代码越多,速度就越慢。可能有 1,200 个方法在运行时执行时进行 JIT 编译,而在解释模式下,只有解释器进行一次 JIT 编译。我可能会在编译模式的循环中看到额外的 JIT 时间。

实验:

  1. 每种模式运行 5 次(只是为了完成任何 JIT 编译和填充缓存)
  2. 运行 50 次,取平均值

结果:

  1. 编译模式:每次运行 1.6 毫秒
  2. 解释模式:每次运行 5.3 毫秒

观察结果:

似乎有大量编译模式FIRST运行期间花费的时间。

编译模式的第二次运行速度已经与解释模式相似。

解释模式不会随着运行次数的增加而显着加速。

因此表明我的观点是规则代码在第一次运行期间是 JIT 编译的。

.NET is a JIT-compiled environment, so the more code to JIT-compile, the slower it is. It can be that 1,200 methods are JIT-compiled at run-time at the point of execution, vs. in the interpreted mode only the interpreter is JIT-compiled, once. I may be seeing extra JIT times in my loops for the compiled mode.

Experiment:

  1. Run each mode 5 times (just to complete any JIT compilation and fill caches)
  2. Time 50 runs, take the average

Results:

  1. Compiled mode: 1.6ms per run
  2. Interpreted mode: 5.3ms per run

Observations:

It appears that a large amount of time is spent during the FIRST run of the compiled mode.

The SECOND run of the compiled mode is already similar in speed to the interpreted mode.

The interpreted mode does not speed up significant with the number of runs.

Thus suggesting my point of view that the rule code is JIT-compiled during the first run.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文