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

相关文章:

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

相关推荐
zjnlswd7 小时前
C#学习笔记4
笔记·学习·c#
曹牧8 小时前
C#:List<string>特定格式输出
windows·c#
Z5998178419 小时前
c#软件开发学习笔记--Modbus-TCP/UDP网口通讯
笔记·学习·c#
曹牧10 小时前
C#:取的多个药品批准文号中的一个
c#
海盗123412 小时前
微软技术周报 · 2026-08-11~2026-08-18
人工智能·microsoft·.net
用户2986985301413 小时前
3 种方法,轻松将 PowerPoint 转换为 PDF 格式
人工智能·后端·c#
KindSuper_liu14 小时前
HybridCLR DHE及MetaVersion工作流核心概念
c#
Leinwin14 小时前
微软MAI-Code-1.1-Flash技术解读:Token效率提升25%,CLI与.NET全面升级
microsoft·.net
淡海水14 小时前
01-08-运行时-虚方法分派与接口调用的底层实现
linux·windows·microsoft·c#·虚方法·vtable·vsd