一个强制转换快还是ToString快,搞得我晕头转向的。以为OK了出结果了,却出现奇怪的情况,还好,总算是山穷水尽(复,李敖说的)疑无路,柳暗花明又一村。
一开始,我想大家都跟我猜想的一样,认为ToString比较慢,而且还有危险。结果一开始我的试验结果跟我的猜测比较吻合,ToString比强制转换要慢一个级别,不同的机子上面会有不同的结果,在10:1到6:1之间。但是后来有人说在Console下面不一样,问题就变得让我困惑起来了。事实上这个跟GDI,跟Form本身,跟DoEvents函数统统没有关系,因为我没有动用到无关的东西,也没有把非必要的部分给测量出来。更让我感到有趣的是,即使是在Console下面,只要自己写的类继承自Form,测试结果就会变慢。如果取消继承关系,两者的速度就会相差无几。认为Form的初始化会比较复杂,会动用Native资源,会在释放对象的时候有效率问题,这些都是错的,因为我很明显没有不断的进行构造和释放,也没有把这部分的时间计算在内。那么到底是什么问题呢?昨天没有时间进行试验,今天一大早起来就是为了进行测试。
其实我一开始就在汇编底下注意到那个ToString之后的调用,总觉得很奇怪:为什么不用强制转换之后的那一个赋值调用呢?(就是下面这个)
00000112 mov esi,eax
00000114 push esi
00000115 mov ecx,edi
00000117 mov edx,995358h
0000011c call 71E7021D
所以我从一开始就怀疑性能损失在这个地方,但是因为暂时没有办法调式到内部(MS的调试器……),所以我只能够进行一些猜想:是不是跟测试所在的实例有关系呢?
用一句代码来举一个例:
s = o.ToString();
本来觉得性能应该仅仅取决于调用了什么方法(ToString还是强制转换),以及所调用对象本身的类型(跟o有关)。比如考虑到ToString是虚函数,也许会有关系。但是后来看到这一系列奇怪的结果之后,不得不怀疑这个测试还跟this有关系。
首先我就猜测,是不是跟继承的深度有关系?如果是这个问题就太荒谬了,但是Form类和自己写的类之间的差别之一就是继承深度,还是有嫌疑的理由。为了验证这个问题,写了两个类:
public class TestBase

{
protected const int testRound = 100000000;
private string sTestBase;
public void Test();
}
public class First : TestBase

{
private string sFirst;
public void FirstTest();
}构造相应的实例并本别调用Test和FirstTest,函数内部的代码大致如下:
object o = "Hello! this is a test!";
long dt;
int i;
dt = DateTime.Now.Ticks;
for (i = 0; i < testRound; i++)

{
sFirst = (string) o;
}
dt = DateTime.Now.Ticks - dt;
Console.WriteLine(dt.ToString("N"));
dt = DateTime.Now.Ticks;
for (i = 0; i < testRound; i++)

{
sFirst = o.ToString();
}
dt = DateTime.Now.Ticks - dt;
Console.WriteLine(dt.ToString("N"));
这里写成两个函数主要是避免虚函数可能带来的影响,尽管不太可能,还是不要节外生枝比较好。测试的结果表明几乎没有任何差别,我还是不死心,把继承深度从1层扩大到3层,测试结果还是一样,因此排除了继承深度带来的影响。
然后我只好怀疑是否因为对象内部实现了某些接口造成性能上面的差异,因为Form内部确是有实现不少的接口,尽管也挺荒谬的。于是我又写了一个类:
public class Second : First, ICloneable, IComparable, IFormattable, IServiceProvider, IDisposable, IConvertible, ICustomFormatter, IAsyncResult, IAppDomainSetup, IDbCommand, IFeatureSupport, IFileReaderService, IContainerControl, IDataAdapter, IButtonControl, ICommandExecutor, IDbDataParameter, IColumnMapping, IMessageFilter, ITableMapping, IWin32Window, IWindowTarget, ITableMappingCollection

{
private string sSecond;
public void SecondTest();
// 接口实现就不贴上来了
}
不要怪我狠心,我开始的时候添加了三个接口,观测到几十毫秒的差异,于是就一路添加上去。最后发现差别不会超过100毫秒,并且很可能是误差造成的。现在添加了二十多个接口都没有问题,那么到底问题在哪里呢?
再看就剩下Form的父类没有研究了,难道还真的是构造函数里面有些损耗性能的东西?那就太郁闷了,也非常荒谬。没办法,猜测不解决问题,还是来测试一下吧。看看怎么个测试法呢?想了想觉得还是从最上面的父类开始找问题,也就是说设计一个从MarshalByRefObject派生的类:
public class Fourth : MarshalByRefObject

{
private string sFourth;
public void FourthTest();
}不测不知道,一测下一跳,问题就是由MarshalByRefObject引起的,试验结果跟前面的10:1非常吻合。后来为了挖掘更多的数据,我做了更多的相关测试,例如测试函数是实例上的,但是sFourth则是静态的结果会怎么样?如果函数是静态的,但是全局变量是实例上的呢?两者都是静态的呢?所以还写了更多的函数来测试:
public void TestStatic();
static public void StaticTest(Fourth fourth);
static public void StaticTestStatic(Fourth fourth);这个测试我在.NET 1.1(DEBUG)、.NET 2.0(DEBUG/RELEASE)下面分别测试,测试结果数据如下:
| 说明 | .NET 1.1 DEBUG | .NET 2.0 DEBUG | .NET 2.0 RELEASE |
| TestBase : object string = (string) o string = o.ToString() First : TestBase string = (string) o string = o.ToString() Second : First, Interfaces string = (string) o string = o.ToString() Third : First string = (string) o string = o.ToString() Fourth : MarshalByRef string = (string) o string = o.ToString() string = MyClass.GetString() string = this.GetString() string = Fourth.GetString() Fourth static string sStatic; public void TestStatic() { sStatic = this.someThing } Fourth private string sFourth; static public void StaticTest(obj) { sFourth = obj.someThing } Fourth static string sStatic; static public void StaticTestStatic() { sStatic = Fourth.SomeThing } ---End of Test--- |
TestBase 10,625,000.00 11,250,000.00 First 10,468,750.00 11,093,750.00 Second 10,781,250.00 10,937,500.00 Third 10,468,750.00 11,093,750.00 Fourth 10,625,000.00 109,062,500.00 110,937,500.00 120,781,250.00 109,062,500.00 Fourth 10,468,750.00 11,093,750.00 11,093,750.00 24,843,750.00 10,156,250.00 Fourth 106,718,750.00 108,125,000.00 109,687,500.00 119,218,750.00 106,875,000.00 Fourth 10,312,500.00 11,093,750.00 11,093,750.00 24,843,750.00 10,156,250.00 DONE |
TestBase 10,781,250.00 15,312,500.00 First 10,312,500.00 15,781,250.00 Second 11,250,000.00 15,468,750.00 Third 10,312,500.00 14,531,250.00 Fourth 9,687,500.00 125,625,000.00 120,312,500.00 131,093,750.00 118,281,250.00 Fourth 8,593,750.00 12,656,250.00 12,968,750.00 27,343,750.00 12,187,500.00 Fourth 117,343,750.00 118,906,250.00 119,687,500.00 131,406,250.00 121,562,500.00 Fourth 8,593,750.00 12,656,250.00 12,968,750.00 28,125,000.00 12,031,250.00 DONE |
TestBase 8,125,000.00 12,968,750.00 First 7,656,250.00 12,968,750.00 Second 8,281,250.00 15,468,750.00 Third 7,187,500.00 13,437,500.00 Fourth 7,812,500.00 12,812,500.00 5,156,250.00 25,625,000.00 6,093,750.00 Fourth 12,812,500.00 15,156,250.00 6,093,750.00 24,687,500.00 6,250,000.00 Fourth 113,750,000.00 115,156,250.00 109,062,500.00 126,718,750.00 109,062,500.00 Fourth 6,875,000.00 12,031,250.00 5,156,250.00 22,968,750.00 5,156,250.00 DONE |