一 简介
1、AScript
AScript是一个开源的C#动态脚本解析执行引擎,支持自定义语法,支持扩展多种脚本语言(目前已支持C#/Python3/SQL/JavaScript/Lua五种语言),支持解释执行和编译执行两种执行模式,并且支持编译缓存,以适配不同场景下对性能的要求。
目前官方最新版本:1.6.0
开源地址:
2、Flee
Flee是一个用于. NET框架的表达式解析器和求值器,可以在运行时计算字符串表达式的值(如:sqrt(a^2 + b^2),其中a、b是变量)它的解析和计算速度是比较快和高效的。
目前官方最新版本:2.0.0
相关文章:
二 性能测试
使用Benchmark工具进行性能基准测试,测试环境:
BenchmarkDotNet v0.14.0, Windows 11 (10.0.22621.4317/22H2/2022Update/SunValley2)
AMD Ryzen 7 7840H with Radeon 780M Graphics, 1 CPU, 16 logical and 8 physical cores
.NET SDK 9.0.316
[Host] : .NET 6.0.36 (6.0.3624.51421), X64 RyuJIT AVX2
DefaultJob : .NET 6.0.36 (6.0.3624.51421), X64 RyuJIT AVX2
安装nuget包:
install-package AScript
install-package Flee
1、常量表达式
1 // 待测试表达式
2 private static readonly string s = "100 * (5 + 5) * (6-2)";
3 // 用于测试结果比较,确保测试执行结果是正确的
4 private static readonly int r = 100 * (5 + 5) * (6 - 2);
分5个测试场景:
1)解释执行
Flee不支持解释执行,对AScript的解释执行模式进行测试,以进行综合评价,测试代码如下:
1 [Benchmark]
2 public void AScript1()
3 {
4 var script = new AScript.Script();
5 var result = script.Eval<int>(s);
6 if (result != r) throw new Exception("result error");
7 }
2)编译执行(无缓存)
1 [Benchmark]
2 public void AScript2_Compile()
3 {
4 var script = new AScript.Script();
5 var result = script.Eval<int>(s, ECompileMode.All);
6 if (result != r) throw new Exception("result error");
7 }
8
9 [Benchmark]
10 public void Flee2()
11 {
12 var context = new Flee.PublicTypes.ExpressionContext();
13 var d = context.CompileGeneric<int>(s);
14 var result = d.Evaluate();
15 if (result != r) throw new Exception("result error");
16 }
3)编译执行(缓存上下文)
1 private static Script _Script3;
2
3 [Benchmark]
4 public void AScript3_Context()
5 {
6 if (_Script3 == null)
7 {
8 _Script3 = new AScript.Script();
9 }
10 var result = _Script3.Eval<int>(s, ECompileMode.All);
11 if (result != r) throw new Exception("result error");
12 }
13
14 private static Flee.PublicTypes.ExpressionContext _LeeContext3;
15
16 [Benchmark]
17 public void Flee3_Context()
18 {
19 if (_LeeContext3 == null)
20 {
21 _LeeContext3 = new Flee.PublicTypes.ExpressionContext();
22 }
23 var d = _LeeContext3.CompileGeneric<int>(s);
24 var result = d.Evaluate();
25 if (result != r) throw new Exception("result error");
26 }
4)编译执行(缓存委托)
此为通用缓存场景,根据字符串表达式缓存编译生成的委托,提高整体性能。AScript已内置缓存功能,Flee需手动添加缓存逻辑。
1 private static Script _Script4;
2
3 [Benchmark]
4 public void AScript4_Cache()
5 {
6 if (_Script4 == null)
7 {
8 _Script4 = new AScript.Script();
9 }
10 var result = _Script4.Eval<int>(s, -1);
11 if (result != r) throw new Exception("result error");
12 }
13
14 private static readonly ConcurrentDictionary<string, Flee.PublicTypes.IGenericExpression<int>> _LeeExpr4Dict = new ConcurrentDictionary<string, Flee.PublicTypes.IGenericExpression<int>>();
15
16 [Benchmark]
17 public void Flee4()
18 {
19 string key = s;
20 if (!_LeeExpr4Dict.TryGetValue(key, out var d))
21 {
22 var context = new Flee.PublicTypes.ExpressionContext();
23 d = context.CompileGeneric<int>(s);
24 _LeeExpr4Dict[key] = d;
25 }
26 var result = d.Evaluate();
27 if (result != r) throw new Exception("result error");
28 }
5)编译执行(委托变量)
此场景为对固定字符串表达式进行编译生成委托,然后再调用(比如循环调用),来提高性能。
1 private static Func<int> _func5;
2
3 [Benchmark]
4 public void AScript5()
5 {
6 if (_func5 == null)
7 {
8 var script = new AScript.Script();
9 _func5 = script.Compile<int>(s);
10 }
11 var result = _func5();
12 if (result != r) throw new Exception("result error");
13 }
14
15 private static Flee.PublicTypes.IGenericExpression<int> _LeeExpr5;
16
17 [Benchmark]
18 public void Flee5()
19 {
20 if (_LeeExpr5 == null)
21 {
22 var context = new Flee.PublicTypes.ExpressionContext();
23 _LeeExpr5 = context.CompileGeneric<int>(s);
24 }
25 var result = _LeeExpr5.Evaluate();
26 if (result != r) throw new Exception("result error");
27 }
测试结果如下:

小结:
- 解释执行模式对比编译执行模式有一定优势,因为编译生成委托成本比较大。
- 编译不缓存委托场景,AScript比Flee性能高,内存占用低。
- 编译缓存委托场景,AScript和Flee相差不大。
2、变量表达式
1 // 待测试表达式,包含a、b、c三个变量
2 private static readonly string s = "a * (b + 5) * (c-2)";
3 // 用于测试结果比较,确保测试执行结果是正确的
4 private static readonly int r = 100 * (5 + 5) * (6 - 2);
同样分5个测试场景:
1)解释执行
1 [Benchmark]
2 public void AScript1()
3 {
4 var script = new AScript.Script();
5 script.Context.SetVar("a", 100);
6 script.Context.SetVar("b", 5);
7 script.Context.SetVar("c", 6);
8 var result = script.Eval<int>(s);
9 if (result != r) throw new Exception("result error");
10 }
2)编译执行(无缓存)
1 [Benchmark]
2 public void AScript2_Compile()
3 {
4 var script = new AScript.Script();
5 script.Context.SetVar("a", 100);
6 script.Context.SetVar("b", 5);
7 script.Context.SetVar("c", 6);
8 var result = script.Eval<int>(s, ECompileMode.All);
9 if (result != r) throw new Exception("result error");
10 }
11
12 [Benchmark]
13 public void Flee2()
14 {
15 var context = new Flee.PublicTypes.ExpressionContext();
16 context.Variables["a"] = 100;
17 context.Variables["b"] = 5;
18 context.Variables["c"] = 6;
19 var d = context.CompileGeneric<int>(s);
20 var result = d.Evaluate();
21 if (result != r) throw new Exception("result error");
22 }
3)编译执行(缓存上下文)
1 private static Script _Script3;
2
3 [Benchmark]
4 public void AScript3_Context()
5 {
6 if (_Script3 == null)
7 {
8 _Script3 = new AScript.Script();
9 _Script3.Context.SetVar("a", 100);
10 _Script3.Context.SetVar("b", 5);
11 _Script3.Context.SetVar("c", 6);
12 }
13 var result = _Script3.Eval<int>(s, ECompileMode.All);
14 if (result != r) throw new Exception("result error");
15 }
16
17 private static Flee.PublicTypes.ExpressionContext _LeeContext3;
18
19 [Benchmark]
20 public void Flee3()
21 {
22 if (_LeeContext3 == null)
23 {
24 _LeeContext3 = new Flee.PublicTypes.ExpressionContext();
25 _LeeContext3.Variables["a"] = 100;
26 _LeeContext3.Variables["b"] = 5;
27 _LeeContext3.Variables["c"] = 6;
28 }
29 var d = _LeeContext3.CompileGeneric<int>(s);
30 var result = d.Evaluate();
31 if (result != r) throw new Exception("result error");
32 }
4)编译执行(缓存委托)
1 private static Script _Script4;
2
3 [Benchmark]
4 public void AScript4_Cache()
5 {
6 if (_Script4 == null)
7 {
8 _Script4 = new AScript.Script();
9 _Script4.Context.SetVar("a", 100);
10 _Script4.Context.SetVar("b", 5);
11 _Script4.Context.SetVar("c", 6);
12 }
13 var result = _Script4.Eval<int>(s, -1);
14 if (result != r) throw new Exception("result error");
15 }
16
17 private static readonly ConcurrentDictionary<string, Flee.PublicTypes.IGenericExpression<int>> _LeeExpr4Dict = new ConcurrentDictionary<string, Flee.PublicTypes.IGenericExpression<int>>();
18
19 [Benchmark]
20 public void Flee4()
21 {
22 if (!_LeeExpr4Dict.TryGetValue(s, out var d))
23 {
24 var context = new Flee.PublicTypes.ExpressionContext();
25 context.Variables["a"] = 100;
26 context.Variables["b"] = 5;
27 context.Variables["c"] = 6;
28 d = context.CompileGeneric<int>(s);
29 _LeeExpr4Dict[s] = d;
30 }
31 var result = d.Evaluate();
32 if (result != r) throw new Exception("result error");
33 }
5)编译执行(委托变量)
1 private static Func<int> _func5;
2
3 [Benchmark]
4 public void AScript5()
5 {
6 if (_func5 == null)
7 {
8 var script = new AScript.Script();
9 script.Context.SetVar("a", 100);
10 script.Context.SetVar("b", 5);
11 script.Context.SetVar("c", 6);
12 _func5 = script.Compile<int>(s);
13 }
14 var result = _func5();
15 if (result != r) throw new Exception("result error");
16 }
17
18 private static Flee.PublicTypes.IGenericExpression<int> _LeeExpr5;
19
20 [Benchmark]
21 public void Flee5()
22 {
23 if (_LeeExpr5 == null)
24 {
25 var context = new Flee.PublicTypes.ExpressionContext();
26 context.Variables["a"] = 100;
27 context.Variables["b"] = 5;
28 context.Variables["c"] = 6;
29 _LeeExpr5 = context.CompileGeneric<int>(s);
30 }
31 var result = _LeeExpr5.Evaluate();
32 if (result != r) throw new Exception("result error");
33 }
测试结果如下:

小结:
- 解释执行模式对比编译执行模式有一定优势。
- 编译无缓存模式,AScript性能比Flee高。
- 编译缓存模式,Flee性能比AScript略微高一点,Flee通过IL生成委托,而AScript通过表达式树生成委托,并且需要支持多语句,所以编译时会定义临时变量来获取上下文中的变量,再进行后续语句执行。
3、函数表达式
1 // 待测试表达式
2 private static readonly string s = "Sum(10, 5)";
3 // 用于测试结果比较,确保测试执行结果是正确的
4 private static readonly int r = MyFunctions.Sum(10, 5);
5
6 public class MyFunctions
7 {
8 public static int Sum(int a, int b)
9 {
10 return a + b;
11 }
12 }
同样分5个测试场景:
1)解释执行
1 [Benchmark]
2 public void AScript1()
3 {
4 var script = new AScript.Script();
5 script.Context.AddFunc(typeof(MyFunctions));
6 var result = (int)script.Eval(s);
7 if (result != r) throw new Exception("result error");
8 }
2)编译执行(无缓存)
1 [Benchmark]
2 public void AScript2_Compile()
3 {
4 var script = new AScript.Script();
5 script.Context.AddFunc(typeof(MyFunctions));
6 var result = script.Eval<int>(s, ECompileMode.All);
7 if (result != r) throw new Exception("result error");
8 }
9
10 [Benchmark]
11 public void Flee2()
12 {
13 var context = new Flee.PublicTypes.ExpressionContext();
14 context.Imports.AddType(typeof(MyFunctions));
15 var d = context.CompileGeneric<int>(s);
16 var result = d.Evaluate();
17 if (result != r) throw new Exception("result error");
18 }
3)编译执行(缓存上下文)
1 private static Script _Script3;
2
3 [Benchmark]
4 public void AScript3_Context()
5 {
6 if (_Script3 == null)
7 {
8 _Script3 = new AScript.Script();
9 _Script3.Context.AddFunc(typeof(MyFunctions));
10 }
11 var result = _Script3.Eval<int>(s, ECompileMode.All);
12 if (result != r) throw new Exception("result error");
13 }
14
15 private static Flee.PublicTypes.ExpressionContext _FleeContext3;
16
17 [Benchmark]
18 public void Flee3()
19 {
20 if (_FleeContext3 == null)
21 {
22 _FleeContext3 = new Flee.PublicTypes.ExpressionContext();
23 _FleeContext3.Imports.AddType(typeof(MyFunctions));
24 }
25 var d = _FleeContext3.CompileGeneric<int>(s);
26 var result = d.Evaluate();
27 if (result != r) throw new Exception("result error");
28 }
4)编译执行(缓存委托)
1 private static Script _Script4;
2
3 [Benchmark]
4 public void AScript4_Cache()
5 {
6 if (_Script4 == null)
7 {
8 _Script4 = new AScript.Script();
9 _Script4.Context.AddFunc(typeof(MyFunctions));
10 }
11 var result = _Script4.Eval<int>(s, -1);
12 if (result != r) throw new Exception("result error");
13 }
14
15 private static readonly ConcurrentDictionary<string, Flee.PublicTypes.IGenericExpression<int>> _FleeExpr4Dict = new ConcurrentDictionary<string, Flee.PublicTypes.IGenericExpression<int>>();
16
17 [Benchmark]
18 public void Flee4()
19 {
20 string key = s;
21 if (!_FleeExpr4Dict.TryGetValue(key, out var d))
22 {
23 var context = new Flee.PublicTypes.ExpressionContext();
24 context.Imports.AddType(typeof(MyFunctions));
25 d = context.CompileGeneric<int>(s);
26 _FleeExpr4Dict[key] = d;
27 }
28 var result = d.Evaluate();
29 if (result != r) throw new Exception("result error");
30 }
5)编译执行(委托变量)
1 private static Func<int> _func5;
2
3 [Benchmark]
4 public void AScript5()
5 {
6 if (_func5 == null)
7 {
8 var script = new AScript.Script();
9 script.Context.AddFunc(typeof(MyFunctions));
10 _func5 = script.Compile<int>(s);
11 }
12 var result = _func5();
13 if (result != r) throw new Exception("result error");
14 }
15
16 private static Flee.PublicTypes.IGenericExpression<int> _FleeExpr5;
17
18 [Benchmark]
19 public void Flee5()
20 {
21 if (_FleeExpr5 == null)
22 {
23 var context = new Flee.PublicTypes.ExpressionContext();
24 context.Imports.AddType(typeof(MyFunctions));
25 _FleeExpr5 = context.CompileGeneric<int>(s);
26 }
27 var result = _FleeExpr5.Evaluate();
28 if (result != r) throw new Exception("result error");
29 }
测试结果如下:

小结:
- 解释执行模式对比编译执行模式有一定优势。
- 编译无缓存模式,AScript性能比Flee高。
- 编译缓存模式,AScript与Flee性能相差不大,AScript低内存占用。
4、多语句(表达式组合)
AScripit支持多语句,Flee通过表达式组合方式实现。
另外,AScript默认是回写临时变量的,所以要关闭变量回写功能,与Flee执行逻辑保持一致。
1 // AScript待测试脚本
2 private static readonly string s = "int m = a * (b + 5) * (c-2); int n = b + a / 10 -c; m + n";
3 // 用于测试结果比较,确保测试执行结果是正确的
4 private static readonly int r = 100 * (5 + 5) * (6 - 2) + 5 + 100 / 10 - 6;
分4个测试场景,由于Flee的表达式组合方式不支持缓存,仅测试AScript的编译缓存模式。
1)解释执行
1 [Benchmark]
2 public void AScript1()
3 {
4 var script = new AScript.Script();
5 script.Context.SetVar("a", 100);
6 script.Context.SetVar("b", 5);
7 script.Context.SetVar("c", 6);
8 var result = (int)script.Eval(s);
9 if (result != r) throw new Exception("result error");
10 }
2)编译执行(无缓存)
1 [Benchmark]
2 public void AScript2_Compile()
3 {
4 var script = new AScript.Script();
5 // 不回写脚本中的临时变量
6 script.Options.RewriteVariables = false;
7 script.Context.SetVar("a", 100);
8 script.Context.SetVar("b", 5);
9 script.Context.SetVar("c", 6);
10 var result = script.Eval<int>(s, ECompileMode.All);
11 if (result != r) throw new Exception("result error");
12 }
13
14 [Benchmark]
15 public void Flee2()
16 {
17 var context = new Flee.PublicTypes.ExpressionContext();
18 context.Variables["a"] = 100;
19 context.Variables["b"] = 5;
20 context.Variables["c"] = 6;
21 var engine = new Flee.CalcEngine.PublicTypes.CalculationEngine();
22 engine.Add("m", "a * (b + 5) * (c-2)", context);
23 engine.Add("n", "b + a / 10 -c", context);
24 engine.Add("result", "m + n", context);
25 int result = engine.GetResult<int>("result");
26 if (result != r) throw new Exception("result error");
27 }
3)编译执行(缓存上下文)
1 private static Script _Script3;
2
3 [Benchmark]
4 public void AScript3_Context()
5 {
6 if (_Script3 == null)
7 {
8 _Script3 = new AScript.Script();
9 // 不回写脚本中的临时变量
10 _Script3.Options.RewriteVariables = false;
11 _Script3.Context.SetVar("a", 100);
12 _Script3.Context.SetVar("b", 5);
13 _Script3.Context.SetVar("c", 6);
14 }
15 var result = _Script3.Eval<int>(s, ECompileMode.All);
16 if (result != r) throw new Exception("result error");
17 }
18
19 private static Flee.PublicTypes.ExpressionContext _FleeContext3;
20
21 [Benchmark]
22 public void Flee3()
23 {
24 if (_FleeContext3 == null)
25 {
26 _FleeContext3 = new Flee.PublicTypes.ExpressionContext();
27 _FleeContext3.Variables["a"] = 100;
28 _FleeContext3.Variables["b"] = 5;
29 _FleeContext3.Variables["c"] = 6;
30 }
31 var engine = new Flee.CalcEngine.PublicTypes.CalculationEngine();
32 engine.Add("m", "a * (b + 5) * (c-2)", _FleeContext3);
33 engine.Add("n", "b + a / 10 -c", _FleeContext3);
34 engine.Add("result", "m + n", _FleeContext3);
35 int result = engine.GetResult<int>("result");
36 if (result != r) throw new Exception("result error");
37 }
4)编译执行(缓存委托)
1 private static Script _Script4;
2
3 [Benchmark]
4 public void AScript4_Cache()
5 {
6 if (_Script4 == null)
7 {
8 _Script4 = new AScript.Script();
9 _Script4.Context.SetVar("a", 100);
10 _Script4.Context.SetVar("b", 5);
11 _Script4.Context.SetVar("c", 6);
12 }
13 var result = _Script4.Eval<int>(s, -1);
14 if (result != r) throw new Exception("result error");
15 }
测试结果如下:

小结:
- 解释执行模式对比编译执行模式有一定优势。
- 编译执行模式,AScript比Flee性能高,内存占用低。
三 总结
从功能、应用场景、性能、内存占用等方面,AScript对比Flee都有绝对的优势:
1)AScript不创建完整语法树,而是边解析表达式边计算,减少节点创建,并且使用节点池来复用节点,达到低内存高性能的优势。
2)AScript默认为解释执行模式,不缓存也能有较高的性能。
3)AScript内置编译缓存功能,支持缓存版本号和定时缓存,不同应用场景使用合适的缓存策略,来提高项目整体性能。
4)AScript支持多语句、变量/函数定义,让脚本更灵活。
5)AScript支持扩展多种脚本语言(目前已支持C#/Python3/SQL/JavaScript/Lua五种语言),项目擅长什么脚本语言就用什么语言,提高脚本配置效率。
6)AScript支持自定义语法,可在项目中定制自己的语法。