其實我自己對執行速度這個問題本來並沒有什麼興趣,因為以前的經驗告訴我:除非是運算密集型的程序,否則腳本語言和編譯型語言使用起來速度沒有多大差別。但是我們公司有個人知道我的想法以後,天天在我耳邊嚷嚷腳本運行速度太慢,那好吧,讓我用實驗來說服你。不過這一試,還真的出現了嚇人一跳的結果。
我構思的實驗覆蓋到下面幾個我認為是實際項目中比較有代表性的場景:
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)
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的時候,引擎只需要加載一次就夠了,所以這個缺點大體上還是可以接受的。