白桦的天空

第一次的心动,永远的心痛!
  首页  :: 新随笔  :: 联系 :: 管理

.NET CF 能不能快一点?

Posted on 2009-04-29 13:09  白桦的天空  阅读(213)  评论(0编辑  收藏  举报

上一期的时候,有人问什么是CF啊?大家都知道BF、GF,CF是什么确实不一定知道。CF就是CompactFramework,可以简单的认为是.NET Framework的简化版,专门用在WinCE系列操作系统上面的。
虽 然说是“简化版本”,但是可能和你想象的不是同一回事。首先它并不是简单的进行简化,而是在很多地方是重新编写的,因此有一些东西的行为可能只是和. NET Framework相似而不是相同。其次尽管进行了简化,性能也仍未达到理想状态,有可能你也会感觉到使用.NET CF开发的软件的运行速度非常的慢。所以我们需要尽力进行优化工作,这一期的内容就是怎么有效地进行优化。

要进行优化,我们必须要有重点。有一个著名的80:20定理大家一定听说过,我们首先要做的就是找出20在哪里。如果用.NET CF开发过很多不同规模和功能的程序,那么就会发现如下几个性能瓶颈:
1、如果你的程序有很多的窗口
2、如果你的窗口有很多的控件
3、如果你用了很多的dll
4、尤其如果你用了XML
5、如果你在多层大循环的时候使用了foreach
6、如果你一下子构造大量的对象的时候

前面1、2、3、4点直接影响着启动的速度,后面的5、6两点则影响着运行的性能(实际上还包括第4点)。首先说一下如何加快启动速度。

实际上启动速度是.NET CF的问题,我们不可能直接优化启动速度,但是我们可以绕弯子。怎么绕法呢?最简单的就是尽量少的使用那些花哨的,并非不可替代的功能,例如XML。

如 果不是必须要读取PC级上面产生的XML,那么你最好自己就不要产生XML,这一个东西对于性能的消耗是非常的严重的。即使是非用不可,我们也不要使用傻 瓜版本的XmlDocument来操作,而应该尽量使用手动版本的XmlReader。原因很简单,因为XmlDocument不会关心你到底需要什么东 西,它直接就把整个的XML读进来再说(包括一定的解析),而很多时候我们实际上只需要整个XML文件的一部分信息,并且读完了之后并不需要不停的修改整 个结构和内容,因此手动来操作XML文件比较明智。

对于窗口太多或者窗口上面的控件太多,首先需要做到的是想办法减少数量,数量不能够减 少就想办法减少层次。例如一个Form上面有一个Panel1,Panel1上面有一个Panel2,Panel2上面有一个Label1……这样的话肯 定有问题。当“减少”是一件不可能的事情的时候,我们就需要进行另外一件事情了——颠倒整个的控件构造过程。简单说就是先产生Label1,然后产生 Panel2,然后通过Label1.Parent = Panel2诸如此类的方法进行优化,更详细的说明请参见过去的一篇文章。 这样一个操作的过程和Vs.Net的IDE是相冲突的,因为Vs.Net不会用这种方式产生代码,并且会在任何一个东西被修改的时候重新产生整个的 InitializeComponenets函数。并且可以看到手动的修改非常的麻烦,而且很容易出错,那么这里我可以给大家提供一个源代码级的优化器(Beta版本,不提供任何保证,保留版权,自由使用和再发布)。优化到这里,启动花费的时间至少应该已经能够缩短50%了(在没有SP2的前提下的保守估计),主要是第二个步骤的功劳。

进行到这里,如果对启动速度仍然感觉非常不满意,那怎么办?这个时候才真正开始我想给大家说的内容,因为这里开始才能够勉强称得上是技巧。

首 先我们需要一个Splash窗口,这个是MS一向的做法,确实能够让人感到时间花费没有那么长。Splash窗口上面的内容就是一个图片,不要有太多的控 件,否则弹出来的速度就不会让你感到满意。接下来就需要真正的“减少”时间花费了。事实上我们通过前面的优化步骤就能够体会到,产生控件的操作需要花费很 长的时间,而事实上我们的软件很可能在一开始会有一个让用户进行选择的步骤——比如选择打开一个文件,或者选择游戏的难度或者关卡。这么一个步骤上面的窗 口一般来说都比较简单,同时用户也需要在这个步骤上面进行一定的思考和选择——换而言之,构造这个窗口并不困难,而用户在进入下一个步骤之前会面对这个窗 口至少1秒钟,这一秒钟里面不会进行其他的复杂运算,理论上就是仅仅在闲等。
说到这里大家一定会一拍脑门——啊,对啊,专门用一个线程在后台进行 其它窗口的初始化不久OK了吗?呵呵,问题并不那么简单。我不太清楚.NET Framework下面是否也这样,但是至少在.NET CF里面就是非常困难的一件事情:如果你在线程A上面产生一个控件B,当线程A结束的时候,控件B也会被Dispose。于是通常你会发现确实已经初始化 成功了,但是一旦回到主线程并使用控件B,就会得到ObjectDisposedException作为礼物。如果在初始化完毕之后将线程A挂起来呢?那 样的话控件B暂时不会被Dispose,但是却会得到“死机”的结果。因为控件B属于线程A,因此对线程A的操作,只要是需要通过发送消息的,都会回到线 程A。因此除非你在线程A上面也实现一个消息循环机制,否则是不可能成功的。是不是就不能够用线程的办法呢?也不是,只是方法比较复杂。首先我们需要将构 造语句和其他语句区分出来,构造语句必须在主线程执行,其他语句在构造语句执行完之后就可以在另外一个线程上后台执行了。这涉及到整个 InitializeCompenent的大修改,我想一定没有几个人愿意这么去做,除非你有自动化工具。
好,如果放弃了线程操作,我们还有什么 样的选择?如果你仍然希望利用那一秒钟的时间,那么有一个稍微简单一点的办法:在弹出了用户选择窗口之后,继续进行其它窗口的初始化,但是需要在 InitializeComponents里面没隔几条语句添加一个Application.DoEvents(),这样能够在一定程度上模拟多线程的效 果。(这个很明显就是以前的VB的伎俩,DoEvents()这个语句就是为了达到这个“龌龊”的目的诞生的。)如果你不喜欢这种不优雅的方式,那么就要 看看下面这个优雅的,但是性能上稍微差一点的方案了。
我们检视一下自己的程序,产生窗口的方式基本上由这两种:1、在main函数里面一次过把所 有的窗口都生成了;2、在需要用到什么窗口的时候再通过new产生。这两种方法实际上都是有缺点的,前者在启动的时候速度特别的慢,后者在每一次显示新的 窗口的时候会有一个比较明显的停顿感。因此我们需要把窗口看成是一个非常珍贵的,代价很高的资源。这个时候Singleton模式就能够派上用场了:所有 的窗口都用Singleton模式实现,这样只有第一次打开窗口的时候才会有停顿感,而启动的时候却不会花费这部分时间。下面是我建议的代码最基本形式:

static public MyForm Instance
{
  get
  {
    CreateInstance();
    return _instance;
  }
}

static public void CreateInstance()
{
  if (_instance == null)
  {
    _instance = new MyForm();
  }
}

static private MyForm _instance;

protected override void Dispose(bool disposing)
{
  ...
  _instance = null;
}

在 这里我并没有使用double-check的方式,因为在.NET CF里面使用多线程实在不是一件明智的事情。既然我不打算用多线程,那么double-check也步是非常必要了。看过我上一期的内容,也许会对 Singleton模式有所警戒,请放心,这个代码应该已经比较安全的了。如果实在不放心,就再添加一个DestroyInstance的函数,里面用 try...finally保护起来进行强制Dispose,并在finally里面把_instance置为null,在退出程序的时候就手动注销。事 实上我说的问题是在你打算使用动态加载代码的方式下才会遇到的,如果不是这个情况,上面那个方法其实就是画蛇添足。

关于foreach,我想不用多说了吧?尽可能使用for吧,最好用一个本地变量表示结束条件。eg:
c = Arr.Length;
for (i = 0; i < c; i++)
{
   // ...
}
虽 然有很多地方都会告诉你,MS的JIT能够进行一些优化,像这样的做法完全没有必要,只需要直接写成for (i = 0; i < Arr.Length; i++)。但是根据我的测试表明,至少在CF下面,这样的优化很有可能不会出现的(甚至我怀疑在.NET Framework下面也一样),尤其是在比较复杂的情况下面。直接这么写也许会有点笨拙,但是如果你在一个三层的嵌套里面进行大量的循环,也许值得这么 去做。如果是一层的小循环,那就不必了。但是必须指出的是,foreach还是要极力避免的。

那么如果是一下子构造大量的对象引起了性能 瓶颈,那应该怎么去处理呢?首先考虑如何降低对象的数量,如果没有办法降低数量就需要考虑延后对象初始化的时间,也许两个设计模式可以帮忙—— FlyWeight和Proxy。当然了,应用每一种设计模式都会有风险存在,上面这两个设计模式也一样。使用这两个模式的时候需要考虑以后进行改动或者 扩充的情况下,是否具有足够的灵活性。一般来说,使用Proxy模式已经能够让你对性能感到满意了,例如一棵有着总共一千个节点的树结构,一般来说我们只 会在一开始的时候显示有限的几个节点,因此延后初始化能够带来“感官上的”性能提升就非常明显了。

 


本文地址:http://read.newbooks.com.cn/info/120022.html