C#脚本引擎之AScript与DynamicExpresso对比
一 简介
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内置编译缓存功能,支持缓存版本号和定时缓存,不同应用场景使用合适的缓存策略,来提高项目整体性能。

浙公网安备 33010602011771号