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 AScriptinstall-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支持自定义语法,可在项目中定制自己的语法。
