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 }

测试结果如下:

0981aff3eb6df6d3bf9a731606f549d6_v2-222c1cfb9842cbb356d09c4ac60c87f1_r

小结:

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

测试结果如下:

c0255b25ec421d0935b82ed49c4090f2_v2-88de12b046d136ef460c763f6866956e_1440w

 小结: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 }

测试结果如下:

d2ab95ed4f613d01696afa58c2d3255c_v2-15193d526f483d369d969b5cb5ad1493_1440w

小结: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 }

测试结果如下:

17785b780bb1db7a86ba9fa3ab2ad054_v2-70ee0931b87fac5df60c6044b61f71cc_1440w

小结: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 }

测试结果如下:

6c34b4003628c6c32cc02a10a24073b4_v2-16e717267dd7fa4f49af18dfb1c8f8b4_1440w

小结:AScript性能高。

三 总结

1)AScript支持多语句,DynamicExpresso不支持,通过多次调用Eval方式来实现多语句;

2)AScript比DynamicExpresso性能高,内存占用低;

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

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

posted @ 2026-08-22 08:41  rockey627  阅读(0)  评论(0)    收藏  举报