一 简介
1、AScript
AScript是一个开源的C#动态脚本解析执行引擎,支持自定义语法,支持扩展多种脚本语言(目前已支持C#/Python3/SQL/JavaScript/Lua五种语言),支持解释执行和编译执行两种执行模式,并且支持编译缓存,以适配不同场景下对性能的要求。
目前官方最新版本:1.6.0
开源地址:
2、DynamicExpresso
DynamicExpresso是一个开源的,用.NET Standard 2.0编写的简单c#语句的解释器,将表达式转换为.NET lambda表达式或委托来执行c#语句。
目前官方最新版本:2.19.3
开源地址:https://github.com/dynamicexpresso/DynamicExpresso
二 性能测试
使用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 DynamicExpresso.Core
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);
分3个测试场景:
1)解释执行
DynamicExpresso不支持解释执行,对AScript的解释执行模式进行测试,以进行综合评价,测试代码如下:
1 [Benchmark]
2 public void AScript1()
3 {
4 var script = new 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 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 Expresso2()
11 {
12 var interpreter = new DynamicExpresso.Interpreter();
13 var result = interpreter.Eval<int>(s);
14 if (result != r) throw new Exception("result error");
15 }
3)编译缓存
此为通用缓存场景,根据字符串表达式缓存编译生成的委托,提高整体性能。AScript已内置缓存功能,DynamicExpresso需手动添加缓存逻辑。
1 private static Script _Script3;
2
3 [Benchmark]
4 public void AScript3_Cache()
5 {
6 if (_Script3 == null)
7 {
8 _Script3 = new Script();
9 }
10 var result = _Script3.Eval<int>(s, -1);
11 if (result != r) throw new Exception("result error");
12 }
13
14 private static readonly ConcurrentDictionary<string, Delegate> _DynamicExpresso3Dict = new ConcurrentDictionary<string, Delegate>();
15
16 [Benchmark]
17 public void Expresso3()
18 {
19 string key = s;
20 if (!_DynamicExpresso3Dict.TryGetValue(key, out var func))
21 {
22 var interpreter = new DynamicExpresso.Interpreter();
23 func = interpreter.ParseAsDelegate<Func<int>>(s);
24 _DynamicExpresso3Dict[key] = func;
25 }
26 var result = ((Func<int>)func)();
27 if (result != r) throw new Exception("result error");
28 }
测试结果如下:

小结:
- 解释执行模式对比编译执行模式有一定优势,因为编译生成委托成本比较大。
- 编译执行模式,AScript比DynamicExpresso性能高。
- 编译缓存模式,DynamicExpresso性能略高于AScript。
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);
分3个测试场景:
1)解释执行
1 [Benchmark]
2 public void AScript1()
3 {
4 var script = new 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 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 Expresso2()
14 {
15 var interpreter = new DynamicExpresso.Interpreter();
16 interpreter.SetVariable("a", 100);
17 interpreter.SetVariable("b", 5);
18 interpreter.SetVariable("c", 6);
19 var result = interpreter.Eval<int>(s);
20 if (result != r) throw new Exception("result error");
21 }
3)编译缓存
DynamicExpresso编译生成委托后,如果再修改上下文中的变量值,委托执行结果不会变,不会动态从上下文获取变量,所以对DynamicExpresso的编译缓存测试没有意义。
1 [Benchmark]
2 public void AScript3_Cache()
3 {
4 var script = new 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, -1);
9 if (result != r) throw new Exception("result error");
10 }
测试结果如下:

小结:AScript性能优势比较明显。
3、LINQ表达式
1 private static readonly List<int> _list = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
2 // 待测试表达式
3 private static readonly string s = "list.Where(a => a % 2 == 0).ToList().Count";
4 // 用于测试结果比较,确保测试执行结果是正确的
5 private static readonly int r = 5;
1)解释执行
1 [Benchmark]
2 public void AScript1()
3 {
4 var script = new Script();
5 script.Context.SetVar("list", _list);
6 var result = script.Eval<int>(s);
7 if (result != r) throw new Exception("result error");
8 }
2)编译执行
DynamicExpresso执行LINQ表达式需要开启InterpreterOptions.LambdaExpressions选项。
1 [Benchmark]
2 public void AScript2_Compile()
3 {
4 var script = new Script();
5 script.Context.SetVar("list", _list);
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 Expresso2()
12 {
13 var options = InterpreterOptions.Default | InterpreterOptions.LambdaExpressions;
14 var interpreter = new DynamicExpresso.Interpreter(options);
15 interpreter.SetVariable("list", _list);
16 var result = interpreter.Eval<int>(s);
17 if (result != r) throw new Exception("result error");
18 }
3)编译缓存
1 [Benchmark]
2 public void AScript3_Cache()
3 {
4 var script = new Script();
5 script.Context.SetVar("list", _list);
6 var result = script.Eval<int>(s, -1);
7 if (result != r) throw new Exception("result error");
8 }
测试结果如下:

小结:AScript性能优于DynamicExpresso
4、生成委托
1 [Benchmark]
2 public void AScript1()
3 {
4 var script = new Script();
5 var func = script.Compile<int, int, int>("a + b", "a", "b");
6 var result = func(4, 6);
7 if (result != 10) throw new Exception("result error");
8 }
9
10 [Benchmark]
11 public void Expresso1()
12 {
13 var interpreter = new DynamicExpresso.Interpreter();
14 var func = interpreter.ParseAsDelegate<Func<int, int, int>>("a + b", "a", "b");
15 var result = func(4, 6);
16 if (result != 10) throw new Exception("result error");
17 }
测试结果如下:

小结:AScript性能高。
5、生成Lambda
仅测试生成lambda性能,不做计算。
1 [Benchmark]
2 public void AScript1()
3 {
4 var script = new Script();
5 Expression<Func<int, int, int>> lambda = script.Lambda<int, int, int>("a + b", "a", "b");
6 if (lambda == null) throw new Exception("result error");
7 }
8
9 [Benchmark]
10 public void Expresso1()
11 {
12 var interpreter = new DynamicExpresso.Interpreter();
13 Expression<Func<int, int, int>> lambda = interpreter.ParseAsExpression<Func<int, int, int>>("a + b", "a", "b");
14 if (lambda == null) throw new Exception("result error");
15 }
测试结果如下:

小结:AScript性能高。
三 总结
1)AScript支持多语句,DynamicExpresso不支持,通过多次调用Eval方式来实现多语句;
2)AScript比DynamicExpresso性能高,内存占用低;
3)AScript默认为解释执行模式,不缓存也能有较高的性能;
4)AScript内置编译缓存功能,支持缓存版本号和定时缓存,不同应用场景使用合适的缓存策略,来提高项目整体性能。