其實我自己對執行速度這個問題本來并沒有什么興趣,因為以前的經驗告訴我:除非是運算密集型的程序,否則腳本語言和編譯型語言使用起來速度沒有多大差別。但是我們公司有個人知道我的想法以后,天天在我耳邊嚷嚷腳本運行速度太慢,那好吧,讓我用實驗來說服你。不過這一試,還真的出現了嚇人一跳的結果。
我構思的實驗覆蓋到下面幾個我認為是實際項目中比較有代表性的場景:
1. 訪問一個稍大的數據表,遍歷所有記錄;
2. 生成并操作一個列表;
3. 生成并操作一個字典;
4. 通過反射動態加載并調用一個方法。
C#部分的代碼,編譯時使用了/debug-和/optimize+:


using?System;
using?System.Data.SqlClient;
using?System.Diagnostics;
using?System.Collections.Generic;
using?System.Reflection;
namespace?Test
{
????class?Test
????{
????????public?static?void?Main(string[]?args)
????????{
????????????Console.WriteLine("C#:");
????????????Measure(TestDb,?"TestDb");
????????????Measure(TestList,?"TestList");
????????????Measure(TestDict,?"TestDict");
????????????Measure(TestReflection,?"TestReflection");
????????}
????????
????????delegate?void?FuncDelegate();
????????
????????static?void?Measure(FuncDelegate?func,?string?funcName)
????????{
????????????Stopwatch?sw?=?new?Stopwatch();
????????????sw.Start();
????????????func();
????????????sw.Stop();
????????????Console.WriteLine("????{0}?used?{1}?ms",?funcName,?sw.ElapsedMilliseconds);
????????}
????????
????????static?void?TestDb()
????????{
????????????using?(SqlConnection?conn?=?new?SqlConnection(connStr))
????????????{
????????????????conn.Open();
????????????????
????????????????SqlCommand?cmd?=?new?SqlCommand(sql,?conn);
????????????????SqlDataReader?reader?=?cmd.ExecuteReader();
????????????????while?(reader.Read())
????????????????{
????????????????????var?id?=?reader["Id"];
????????????????????var?code?=?reader["Code"];
????????????????????var?cargoCode?=?reader["CargoCode"];
????????????????????var?length?=?reader["Length"];
????????????????????var?width?=?reader["Width"];
????????????????????var?height?=?reader["Height"];
????????????????????var?vol?=?reader["Vol"];
????????????????????var?pallet?=?reader["Pallet"];
????????????????}
????????????????reader.Close();
????????????????cmd.Dispose();
????????????????conn.Close();
????????????}
????????}
????????
????????static?void?TestList()
????????{
????????????var?list?=?new?List<string>();
????????????const?int?count?=?100000;
????????????for?(int?i=0;?i<count;?i++)
????????????????list.Add(string.Format("item{0}",?i));
????????????for?(int?i=count-1;?i>=0;?i--)
????????????????list.RemoveAt(i);
????????}
????????
????????static?void?TestDict()
????????{
????????????var?dict?=?new?Dictionary<string,?string>();
????????????const?int?count?=?100000;
????????????for?(int?i=0;?i<count;?i++)
????????????????dict[string.Format("key{0}",?i)]?=?string.Format("value{0}",?i);
????????????for?(int?i=0;?i<count;?i++)
????????????????dict.Remove(string.Format("key{0}",?i));
????????}
????????
????????static?void?TestReflection()
????????{
????????????Assembly?assem?=?Assembly.LoadFrom("Lib.dll");
????????????Type?type?=?assem.GetType("Lib.TestLib");
????????????const?int?count?=?100000;
????????????ConstructorInfo?ci?=?type.GetConstructor(Type.EmptyTypes);
????????????MethodInfo?mi?=?type.GetMethod("GetMessage");
????????????for?(int?i=0;?i<count;?i++)
????????????{
????????????????object?obj?=?ci.Invoke(null);?//?Activator.CreateInstance(type);?
????????????????mi.Invoke(obj,?new?object[]?{?"name"?}?);
????????????}
????????}
????????
????????const?string?connStr?=?"Integrated?Security=SSPI;?Initial?Catalog=test;?Data?Source=.";
????????
????????const?string?sql?=?"select?*?from?CargoPackageTypes";
????}
}
?IronPython部分的代碼:


from?__future__?import?with_statement
import?clr,?sys
clr.AddReference('System.Data')
from?System.Data.SqlClient?import?SqlCommand,?SqlConnection
from?System.Diagnostics?import?Stopwatch
from?System.Reflection?import?Assembly
connStr?=?"Integrated?Security=SSPI;?Initial?Catalog=test;?Data?Source=.";
sql?=?"select?*?from?CargoPackageTypes";
def?testDb():
????with?SqlConnection(connStr)?as?conn:
????????conn.Open()
????????
????????cmd?=?SqlCommand(sql,?conn)
????????reader?=?cmd.ExecuteReader()
????????while?reader.Read():
????????????id?=?reader["Id"]
????????????code?=?reader["Code"]
????????????cargoCode?=?reader["CargoCode"]
????????????length?=?reader["Length"]
????????????width?=?reader["Width"]
????????????height?=?reader["Height"]
????????????vol?=?reader["Vol"]
????????????pallet?=?reader["Pallet"]
????????reader.Close()
????????cmd.Dispose()
????????conn.Close()
def?testList():
????lst?=?[]
????count?=?100000
????for?i?in?xrange(count):
????????lst.append('item%d'?%?i)
????for?i?in?xrange(count-1,?-1,?-1):
????????lst.pop(i)
def?testDict():
????d?=?{}
????count?=?100000
????for?i?in?xrange(count):
????????d['key%d'?%?i]?=?'value%d'?%?i
????for?i?in?xrange(count):
????????d.pop('key%d'?%?i)
????????
//www.elivn.com
def?testReflection():
????clr.AddReferenceToFile('Lib.dll')
????from?Lib?import?TestLib
????count?=?100000
????for?i?in?xrange(count):
????????obj??=?TestLib()
????????obj.GetMessage('name')
????????
????????
def?measure(fn):
????sw?=?Stopwatch()
????sw.Start()
????fn()
????sw.Stop()
????print?'????%s?used?%s?ms'?%?(fn.__name__,?sw.ElapsedMilliseconds)
????
print?'Python:'????
measure(testDb)
measure(testList)
measure(testDict)
measure(testReflection)
?運行結果:
?
對于列表和字典的操作,IronPython比C#慢3到4倍,這是意料之中的事情。沒有想到的是訪問數據庫的方法,IronPython竟然比C#還要略快,這是事先無論如何都沒有料到的。原來我以為,數據庫訪問代碼基本上是純粹的調用ADO.Net,瓶頸主要是在數據庫那一邊,IronPython在方法調用的時候應該比C#略微慢一點吧,那么總體速度也應該稍微慢一點才對。沒想到結果正好反過來!我也沒有辦法解釋為什么這里IronPython能夠做到比C#還快。不過結論應該很明顯了:訪問數據庫的時候,你無需擔心IronPython不夠快。我們的項目大多數時候效率瓶頸都是出在數據庫上面,至于程序語言快一點還是慢一點通常無關緊要,更何況這里的結果表明腳本語言有時候反而可能更快呢。
?對于反射的測試,IronPython則是壓倒性的戰勝了C#。需要說明的一點是我在C#中反射生成對象使用的方法是ConstructorInfo.Invoke()。如果換成Activator.CreateInstance()的話,那么C#的時間將會縮減到230~250毫秒,不過即便這樣仍然比IronPython落后一半左右。為什么使用反射時IronPython比C#快這么多呢?或許因為它運行的時候能夠在內存中動態生成部分字節碼,從而跳過反射環節,所以更快吧。
從這個實驗的結果看,IronPython的性能可以說好到超出了我的預期。因為之前也看過其他一些相關的性能評測,比如說Ruby要比Java的運行速度慢30倍(這個比較已經有一段時間了,現在差距應該有所縮小),相比之下IronPython的性能簡直可以用十分優異來形容了。當然腳本語言也有一個不足的地方,就是加載解釋器的時候會帶來幾秒鐘的固定開銷,頻繁修改程序的時候,這幾秒鐘還是有點讓人難受的。好在以嵌入方式使用IronPython的時候,引擎只需要加載一次就夠了,所以這個缺點大體上還是可以接受的。
?補充: 經網友提醒,數據庫緩存實際上對測試結果有一定影響,執行相同的sql語句兩次(雖然是在兩個進程),后一次總是比前一次稍微快一點,不論使用何種語言。通過改變C#和IronPython測試順序以后的結果來看,可以認為數據庫訪問,C#和IronPython的性能基本上是沒有什么差別的。