原来是 MarshalByRefObject 搞的鬼……

一个强制转换快还是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类和自己写的类之间的差别之一就是继承深度,还是有嫌疑的理由。为了验证这个问题,写了两个类:

None.gif public   class  TestBase
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif   
protected const int testRound = 100000000;
InBlock.gif   
private string sTestBase;
InBlock.gif   
public void Test();
ExpandedBlockEnd.gif}
None.gif public   class  First : TestBase
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif   
private string sFirst;
InBlock.gif   
public void FirstTest();
ExpandedBlockEnd.gif}

构造相应的实例并本别调用Test和FirstTest,函数内部的代码大致如下:

None.gif object  o  =   " Hello! this is a test! " ;
None.gif
long  dt;
None.gif
int  i;
None.gif
None.gifdt 
=  DateTime.Now.Ticks;
None.gif
for  (i  =   0 ; i  <  testRound; i ++ )
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    sFirst 
= (string) o;
ExpandedBlockEnd.gif}

None.gifdt 
=  DateTime.Now.Ticks  -  dt;
None.gifConsole.WriteLine(dt.ToString(
" N " ));
None.gif
None.gifdt 
=  DateTime.Now.Ticks;
None.gif
for  (i  =   0 ; i  <  testRound; i ++ )
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    sFirst 
= o.ToString();
ExpandedBlockEnd.gif}

None.gifdt 
=  DateTime.Now.Ticks  -  dt;
None.gifConsole.WriteLine(dt.ToString(
" N " ));
None.gif


这里写成两个函数主要是避免虚函数可能带来的影响,尽管不太可能,还是不要节外生枝比较好。测试的结果表明几乎没有任何差别,我还是不死心,把继承深度从1层扩大到3层,测试结果还是一样,因此排除了继承深度带来的影响。
然后我只好怀疑是否因为对象内部实现了某些接口造成性能上面的差异,因为Form内部确是有实现不少的接口,尽管也挺荒谬的。于是我又写了一个类:

None.gif 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
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    
private string sSecond;
InBlock.gif    
public void SecondTest();
InBlock.gif    
// 接口实现就不贴上来了
ExpandedBlockEnd.gif
}

None.gif

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

None.gif public   class  Fourth : MarshalByRefObject
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    
private string sFourth;
InBlock.gif    
public void FourthTest();
ExpandedBlockEnd.gif}

不测不知道,一测下一跳,问题就是由MarshalByRefObject引起的,试验结果跟前面的10:1非常吻合。后来为了挖掘更多的数据,我做了更多的相关测试,例如测试函数是实例上的,但是sFourth则是静态的结果会怎么样?如果函数是静态的,但是全局变量是实例上的呢?两者都是静态的呢?所以还写了更多的函数来测试:

None.gif public   void  TestStatic();
None.gif
static   public   void  StaticTest(Fourth fourth);
None.gif
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

(先给大家说一下,大家可以注意一下.NET 2.0 RELEASE的数据,相比较起来是一个比较奇特的数据。)

我一开始万万没有想到,a = b 这样的简单赋值语句还会跟a这个变量声明的位置有关系(都是全局变量的情况下),或者说跟a所在的类有关系。以为不就是把b赋给a嘛,汇编里面一句mov或者lea就搞定了,如何获得b才是影响性能的关键。结果发现.NET下面并不是这么简单的事情,至少会有MarshalByRefObject这个特例。以前也听说过MarshalByRefObject会非常的影响性能,但是文章里面没有提及,自己也没有考究过。心里面想,这个MarshalByRefObject跟我有什么关系?现在想想好象还是有一点点关系的,比如你在Form上面有一个全局变量(包括你放在窗口上面的所有控件都是本地变量),你对他进行赋值就会有性能损耗。然而值得庆幸的是,我们很多时候都不会对一个全局变量不停的进行复制操作,比如我们总不可能对一个Button myButton这个全局变量循环赋值100000000次吧?顶多我们可能对Hashtable ht这个全局变量不停的添加内容,还好Hashtable等集合类型都不是从MarshalByRefObject里面继承出来的,如果是那样的话真是要疯掉了……

这个发现也许有用,也许没有用,至少在VS2k5里面已经没有什么太大意义了。但是在.NET CF里面就不好说了,虽然我从直观上认为不可能做出太多优化,并且那里面的MarshalByRefObject里面没有任何内容,不知道.NET CF还会不会对其进行特殊对待呢?
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值