以下以一個簡單的HelloWord程序為例,來分析csscript腳本引擎的性能。
1 class HelloWorld 3 { 4 public void SayHello() 5 { 6 Console.WriteLine("Hello World, from internal!"); 7 } 8 }
一、測試環境
運行的機器硬件配置:Intel Dore Duo CPU,內存 4;
開發環境: vs2010;
二、使用程序內部類和使用腳本的性能比較
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,所以即使拋開動態編譯的成本,這個函數調用,由于內部其實是使用反射的機制來實現的,所以性能損失也比較明顯。
三、一次動態編譯多次調用
測試代碼:
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 }
四、多次動態編譯同一個腳本并調用方法的性能分析
測試代碼:
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方法),每個對象第一次調用該方法時,都需要使用反射方式去查找,所以此時性能較低;
五、動態編譯多個不同的腳本的性能分析
測試代碼:
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)加載到內存中,這個過程可能比較花時間,而動態編譯本身是很快的。
六、結論
(1)在使用cs-script腳本引擎的時候,該程序第一次做動態編譯時,需要有個1s左右的初始化時間;
(2)對于腳本中類的對象的方法的調用,在第一次調用某個對象的方法時(比如上文的HelloWorld類的hell對象的SayHello()方法),由于要使用反射方式去查找該犯法的委托,所以相比原生的對象方法調用要多10ms左右,后續的調用則和原生的方法差不多。
(3)cs-script編譯一個普通源文件的時間基本是毫秒級別,一般在10ms以內,對于一般腳本數量不是很多(比如幾十個)的情況,一般也就是多花幾百毫秒,基本可以忽略;
綜上,在引入了cs-script腳本引擎之后,在享受了腳本所帶來的動態特性的同時,只是在初始化的時候需要多花1s左右的時間,其他情況的性能損失基本可以忽略。
七、相關源碼
本系列包括:
![]() |
不含病毒。www.avast.com |