其实我自己对执行速度这个问题本来并没有什么兴趣,因为以前的经验告诉我:除非是运算密集型的程序,否则脚本语言和编译型语言使用起来速度没有多大差别。但是我们公司有个人知道我的想法以后,天天在我耳边嚷嚷脚本运行速度太慢,那好吧,让我用实验来说服你。不过这一试,还真的出现了吓人一跳的结果。
其实我自己对执行速度这个问题本来并没有什么兴趣,因为以前的经验告诉我:除非是运算密集型的程序,否则脚本语言和编译型语言使用起来速度没有多大差别。但是我们公司有个人知道我的想法以后,天天在我耳边嚷嚷脚本运行速度太慢,那好吧,让我用实验来说服你。不过这一试,还真的出现了吓人一跳的结果。
我构思的实验覆盖到下面几个我认为是实际项目中比较有代表性的场景:
1. 访问一个稍大的数据表,遍历所有记录;
2. 生成并操作一个列表;
3. 生成并操作一个字典;
4. 通过反射动态加载并调用一个方法。
C#部分的代码,编译时使用了/debug-和/optimize+:
Code
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部分的代码:
Code
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的时候,引擎只需要加载一次就够了,所以这个缺点大体上还是可以接受的。
补充: 经网友提醒,数据库缓存实际上对测试结果有一定影响,执行相同的sql语句两次(虽然是在两个进程),后一次总是比前一次稍微快一点,不论使用何种语言。通过改变C#和IronPython测试顺序以后的结果来看,可以认为数据库访问,C#和IronPython的性能基本上是没有什么差别的。