编译表达式的运行速度比解释版本慢得多
我有一个规则引擎,它支持两种操作模式:
- 编译成 C# 程序并链接到引擎
- 解析成基于反向抛光堆栈的指令并解释
规则是带有函数调用的简单算术表达式(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:
- Compilation into a C# program and linked into the engine
- 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
.NET 是一个 JIT 编译环境,因此 JIT 编译的代码越多,速度就越慢。可能有 1,200 个方法在运行时执行时进行 JIT 编译,而在解释模式下,只有解释器进行一次 JIT 编译。我可能会在编译模式的循环中看到额外的 JIT 时间。
实验:
结果:
观察结果:
似乎有大量编译模式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:
Results:
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.