C#脚本引擎 CS-Script 之(二)——性能评测

如下以一个简单的HelloWord程序为例,来分析csscript脚本引擎的性能。css

1 class HelloWorld
3 {
4     public void SayHello()
5     {
6         Console.WriteLine("Hello World, from internal!");
7     }
8 }

 

1、测试环境html

运行的机器硬件配置:Intel Dore Duo CPU,内存 4缓存

开发环境: vs2010ide

2、使用程序内部类和使用脚本的性能比较函数

 1  static void Main(string[] args)
 2         {
 3             CallFromInternal();
 4             CallFromScript();
 5         }
 6 
 7         static void CallFromInternal()
 8         {
 9             Console.WriteLine("");
10             Console.WriteLine("CallFromInternal");
11             DateTime beginTime = DateTime.Now;
12 
13             HelloWorld hello = new HelloWorld();
14             TimeSpan span = DateTime.Now - beginTime;
15             Console.WriteLine("create instance timespan: {0}", span);
16             beginTime = DateTime.Now;
17             hello.SayHello();
18 
19             span = DateTime.Now - beginTime;
20             Console.WriteLine("call helloWorld timespan: {0}", span);
21         }
22 
23 
24         static void CallFromScript()
25         {
26             Console.WriteLine("");
27             Console.WriteLine("CallFromScript");
28             DateTime beginTime = DateTime.Now;
29             
30             dynamic hello = CSScript.Evaluator.LoadFile("HelloWorld.cs");
31             TimeSpan span = DateTime.Now - beginTime;
32             Console.WriteLine("load and precompile script file, timespan= {0}", span);
33 
34             beginTime = DateTime.Now;
35             hello.SayHello();
36 
37             span = DateTime.Now - beginTime;
38             Console.WriteLine("call helloWorld timespan: {0}", span);
39         }

从以上两个函数的输出结果来看,直接调用程序内部函数的时间大概是2ms,而经过脚本引擎来一样一个HelloWorld的时间就达到了835ms,时间差距有400倍源码分析

835ms中,动态编译及其对象建立就花了814ms,而函数调用则21ms,因此即便抛开动态编译的成本,这个函数调用,因为内部实际上是使用反射的机制来实现的,因此性能损失也比较明显。性能

3、一次动态编译屡次调用测试

测试代码:lua

 1 static void CallFromSameScriptLoad1TimeAndCall4Times()
 2         {
 3             Console.WriteLine("");
 4             Console.WriteLine("CallFromSameScriptLoad1TimeAndCall4Times");
 5             DateTime beginTime = DateTime.Now;
 6             TimeSpan span;
 7             dynamic hello = CSScript.Evaluator.LoadFile("HelloWorld.cs");
 8             span = DateTime.Now - beginTime;
 9             Console.WriteLine("load and precompile script file, timespan= {0}", span);
10             
11             for (int i = 0; i < 4; ++i)
12             {
13                 beginTime = DateTime.Now;
14                 hello.SayHello();
15                 span = DateTime.Now - beginTime;
16                 Console.WriteLine("call helloWorld {0} time, timespan: {1}", i+1, span);
17                 Console.WriteLine("");
18             }
19         }

 

     行结果以下, 能够看出,第一次调用花了21ms,后面3次调用的时间基本能够忽略。那么推测,第一次是由于须要经过反射的方式找到SayHello方法的引用,后面的几回调用估计已经把该方法的引用缓存了,就能够直接前面查找好的委托,少了一个经过反射查找的过程,因此速度基本和调用本地方法至关。
以上只是推测,后续须要查阅源码分析看看。

 


4、屡次动态编译同一个脚本并调用方法的性能分析spa

测试代码:

 1  static void CallFromSameScriptLoadAndCall4Times()
 2         {
 3             Console.WriteLine("");
 4             Console.WriteLine("CallFromSameScriptLoadAndCall4Times");
 5 
 6             TimeSpan span;
 7             for (int i = 0; i < 4; ++i)
 8             {
 9                 DateTime beginTime = DateTime.Now;       
10                 dynamic hello = CSScript.Evaluator.LoadFile("HelloWorld.cs");
11                 span = DateTime.Now - beginTime;
12                 Console.WriteLine("load and precompile script file, {0}, timespan= {1}", i+1, span);
13                 beginTime = DateTime.Now;
14                 hello.SayHello();
15 16 
17                 span = DateTime.Now - beginTime;
18                 Console.WriteLine("call helloWorld {0} time, timespan: {1}", i+1, span);
19                 Console.WriteLine("");
20             }            
21         }

 

测试结果以下,第一次调用的时间花销大,后续的时间花销基本至关于上一节中的第一次调用方法的时间。

那么推测:

(1) 对于同一个cs源文件,第一次编译以后会缓存,会把程序集缓存到内存中,后续再调用LoadFile的时候,实际上加载的是内存中缓存的程序集;

(2)第二次及其后续调用LoadFile("HelloWorld.cs"),实际上都是使用内存中的程序集,可是经过反射的方式找到HelloWorld类仍是要每次去作的,因此通常还须要花费3ms左右;

(3)而后因为每一个循环中都从新建立了一个新的HelloWord对象,在每一个循环中去调用hello.SayHello();的时候,实际上仍是实时的使用反射机制去查找hello对象中的SayHello方法,因此这里的时间花销省不了,通常也须要花费7ms左右。

 

推测:

(1)同一个程序集,屡次编译,会使用第一次编译缓存的程序集;

(2)同一个类的多个对象的同一个方法(好比HelloWord类的SayHello方法),每一个对象第一次调用该方法时,都须要使用反射方式去查找,因此此时性能较低;

5、动态编译多个不一样的脚本的性能分析

测试代码:

 1 static void CallFromMultiScriptLoadAndCall4Times()
 2         {
 3             Console.WriteLine("");
 4             Console.WriteLine("CallFromMultiScriptLoadAndCall4Times");
 5 
 6             TimeSpan span;
 7             for (int i = 0; i < 4; ++i)
 8             {
 9                 DateTime beginTime = DateTime.Now;
10                 string fileName = string.Format("HelloWorld{0}.cs", i + 1);
11                 dynamic hello = CSScript.Evaluator.LoadFile(fileName);
12                 span = DateTime.Now - beginTime;
13                 Console.WriteLine("load and precompile script file{2}, {0}, timespan= {1}", i+1, span, fileName);
14                 beginTime = DateTime.Now;
15                 hello.SayHello();
16                 span = DateTime.Now - beginTime;
17                 Console.WriteLine("call helloWorld {0} time, timespan: {1}", i+1, span);
18             }
19         }

 

测试结果以下:

 这里分别动态编译了四个源文件,并调用对应的方法。可是只有第一次编译的时候速度慢,后续三次动态编译的速度和上一节动态编译同一个源文件的速度同样快。到这里就推翻了上一节的结论,说明上一节中2~4次的动态编译速度快,不是由于缓存了第一次动态编译的程序集。那么推测多是由于第一次要动态编译的时候,程序要将.NET的用于动态编译的程序集(CSharpCodeProvider)加载到内存中,这个过程可能比较花时间,而动态编译自己是很快的。

6、结论

(1)在使用cs-script脚本引擎的时候,该程序第一次作动态编译时,须要有个1s左右的初始化时间;

(2)对于脚本中类的对象的方法的调用,在第一次调用某个对象的方法时(好比上文的HelloWorld类的hell对象的SayHello()方法),因为要使用反射方式去查找该犯法的委托,因此相比原生的对象方法调用要多10ms左右,后续的调用则和原生的方法差很少。

(3)cs-script编译一个普通源文件的时间基本是毫秒级别,通常在10ms之内,对于通常脚本数量不是不少(好比几十个)的状况,通常也就是多花几百毫秒,基本能够忽略;

综上,在引入了cs-script脚本引擎以后,在享受了脚本所带来的动态特性的同时,只是在初始化的时候须要多花1s左右的时间,其余状况的性能损失基本能够忽略。

 7、相关源码

CSScript系列之(二)——性能评测.zip

 

 

本系列包括:

C#脚本引擎 CS-Script 之(一)——初识 

C#脚本引擎 CS-Script 之(二)——性能评测 

C#脚本引擎CS-Script之(三)——如何部署 

相关文章
相关标签/搜索