财神机器人

番摊机器人,顶尖机器人,数聊机器人,粮草机器人

数聊机器人 AScript是一个开源的C#动态脚本解析执行引擎

AScript是一个开源的C#动态脚本解析执行引擎,支持自定义语法,支持扩展多种脚本语言(目前已支持C#/Python3/SQL/JavaScript/Lua五种语言),支持解释执行和编译执行两种执行模式,并且支持编译缓存,以适配不同场景下对性能的要求。

目前官方最新版本:1.6.0

开源地址:

2、Flee

Flee是一个用于. NET框架的表达式解析器和求值器,可以在运行时计算字符串表达式的值(如:sqrt(a^2 + b^2),其中a、b是变量)它的解析和计算速度是比较快和高效的。

目前官方最新版本:2.0.0

相关文章:

.NET 中表达式动态解析和计算 Flee 用起来真香

二 性能测试

使用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 }
复制代码

测试结果如下:

207af8e1aebab349e58171bfb29df19d_v2-541a2a9da001406494a3aab81b0b7573_1440w_source=ccfced1a

 小结:

  • 解释执行模式对比编译执行模式有一定优势,因为编译生成委托成本比较大。
  • 编译不缓存委托场景,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 }
复制代码

测试结果如下:

8a9266c66aa774491c419225bbe2a8f5_v2-f8eeeaf7bf826cf50827167c39765edb_1440w_source=ccfced1a

小结:

  • 解释执行模式对比编译执行模式有一定优势。

  • 编译无缓存模式,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 }
复制代码

测试结果如下:

5ef86e607f823aca92a625c6dcb41d0d_v2-1e19454239b1d626d698807e5bf7b5cc_1440w_source=ccfced1a

小结:

  • 解释执行模式对比编译执行模式有一定优势。

  • 编译无缓存模式,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 }
复制代码

测试结果如下:

5a8eb0564afa40312483ef6effcdc181_v2-d487154846b6df3c1a053f96603e4dc9_1440w_source=ccfced1a

 小结:

  • 解释执行模式对比编译执行模式有一定优势。

  • 编译执行模式,AScript比Flee性能高,内存占用低。

三 总结

从功能、应用场景、性能、内存占用等方面,AScript对比Flee都有绝对的优势:

1)AScript不创建完整语法树,而是边解析表达式边计算,减少节点创建,并且使用节点池来复用节点,达到低内存高性能的优势。

2)AScript默认为解释执行模式,不缓存也能有较高的性能。

3)AScript内置编译缓存功能,支持缓存版本号和定时缓存,不同应用场景使用合适的缓存策略,来提高项目整体性能。

4)AScript支持多语句、变量/函数定义,让脚本更灵活。

5)AScript支持扩展多种脚本语言(目前已支持C#/Python3/SQL/JavaScript/Lua五种语言),项目擅长什么脚本语言就用什么语言,提高脚本配置效率。

6)AScript支持自定义语法,可在项目中定制自己的语法。


Powered By Z-BlogPHP 1.7.3

财神机器人,顶尖机器人,数聊机器人,粮草机器人