C#脚本引擎之AScript与Flee对比
一 简介
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支持自定义语法,可在项目中定制自己的语法。

浙公网安备 33010602011771号