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之(三)——如何部署 

相關文章
相關標籤/搜索