如何优化.NET Micro Framework的性能

海外服务器 (510) 2015-11-25 13:36:56

.NET Micro Framework的可剪裁性,高定执行,和天生对硬件高集成度都让它的前途一片光明。当然,它现在还很年轻,就发布的SDK v3.0来看,它还有很长的路要走。
废话不说,就这几个月我用下来的经验谈谈在针对.NET Micro Framework应用程序的性能优化吧。

1. 尽可能减少方法调用!

方法调用过于频繁对于性能的影响非常大,所以所有的优化都是以这个为大前提的。

2. 尽可能避免使用属性,而用公共域来代替。

因为编辑器会在编译的时候为每个属性的getter和setter添加访问方法,基于第一条,这是要避免地。

当然也不是说完全避免使用属性了,毕竟有的时候属性是很方便且必要的。

比如这个下面这个使用属性的例子:

public class Test  

{  

public string Name { get; set; }  

需要把它改成这样:

public class Test  

{  

public string Name;  

3. 只在构造函数里面初始化变量。

这一条很容易明白,看下面的例子:

public class Test  

{  

private string name = "Test String";  

private DateTime date = DateTime.Now;  

private int score, counter;  

public Test()  

{  

score = 0;  

counter = 0;  

}  

初始化的工作其实进行了两次,一次是在声明变量的时候,另外一次是在调用构造函数的时候。遵循第一条原则,我们要尽可能减少方法调用,且构造函数的使用概率很高,所以我们在此需要把初始化的工作全部集中到构造函数里面来进行。

4. 只在必要的地方调用lock。

对于MF这样一个半实时的系统来说,lock的成本远远高于我们的想象。在.NET里面可能感觉不出来,但到了MF这个小伙子手里感觉就非常明显了,也许这一条大家已经知道了,就当我老调重弹吧。

看这个例子:

public class Test  

{  

private ArrayList objs;  

public void SomeMethod(object o)  

{  

for(int i = 0; i < 100; i++)  

{  
9.   if (objs.Contains(o))  

{  

lock(objs.SyncRoot)  

{  

objs.Remove(o);  

}  

}  

}  

}  

在一个循环里面增删一个集合,由于是多线程访问,所以在操作之前加了锁。之所以在循环内部加锁,理由可能是想尽可能的减少lock的访问次数,只有满足那个if条件的时候才会被调用。

实际上,这个想法错了,无论如何,这里的lock都会被调用很多次,这些开销加起来就会对性能造成很大的影响。

把代码改成这样就会好很多:

public class Test  

{  

private ArrayList objs;  
 
public void SomeMethod(object o)  

{  

lock(objs.SyncRoot)  

{  

for(int i = 0; i < 100; i++)  

  {  

   if (objs.Contains(o))  

   {  

    objs.Remove(o);  

   }  

  }  

 }  

}  

5. 保证每个时间只有一个线程在运行。

用惯了.NET,来到MF世界第一个不适应就是它的多线程太慢了,如果同时打开两个线程工作,那么整个程序的效率都会受到极大的影响。

拿电子地图软件来做例子,主线程负责更新UI,工作线程负责在后台取得地图块。这样的设计本身无可厚非也是合理的,但最后我们发现性能实在太差了。

后来更改成为当用户在操作UI的时候,工作线程全都暂停,只有检测到用户没有任何操作的时候才进行工作。

要实现这一点,就要求程序在设计的时候就考虑到工作线程的可暂停性。

6. 尽可能少的并且在最小的范围内调用Invalidate()方法。

很多人在重画UI之后都会习惯性的调用顶层元素的Invalidate()方法来更新所有子控件,因为这样是最快捷的。可很多时候我们忽略了一点,Invalidate()这个方法可能在背后已经被调用过很多次了。

比如,有的控件会在得到焦点的时候调用这个方法,有的控件会在出发用户事件的时候自动调用这个方法。因为这些都是在背后发生的,我们可能并不知情,所以在完成我们自己控件的绘制之后通常会调用parent的Invalide来更新整个布局,这样就会在不知不觉之间导致了不必要的重画产生。

要避免这个问题也很简单,一则仔细观察,二则用Refactor!去阅读一下别人的代码。

7. 尽可能少的使用图片资源。

因为MF本身的数据吞吐量很小,如果载入过多图片资源的话,轻则程序运行效率变低,重则出现内存溢出。所以这里的原则我们要参照网页的设计原则,例如一个按钮图片,把它切割成几个小块,利用重复贴图来完成中间部分,而不要直接使用一整张图片。

同样在制作高亮的时候可以通过改变图片透明度或者在图片上面加一层透明矩形来实现。

8. 仅导入必要的字体资源。

这一点和上一条的理由是一样的,都是减少运行期间的数据吞吐量。对于英文来说还好,本来就不大,对于中文来说就很重要了,因为中文字体动辄就是几百k上兆,如果全部导入的话简直就是灾难。

最好就是程序用到多少就导入多少,实在没办法,就把生僻字全部剔出吧。

9. 窗体最好用完就是立即关闭。
这一点对于窗体很多的应用程序非常重要!在.NET的世界里,打开一个主窗体,然后在主窗体里面创建子窗体的做法非常常见。但这可能会成为你的MF程序运行效率最大的隐性杀手。

例如 主窗体 -> 产品列表 -> 产品详细信息 -> 产品操作窗口 -> 结算窗口

这是一个常见的逻辑线,此时一共有五个窗口被打看,如果你有时间尝试的话,会发现在打开结算窗口的时候,整个程序已经气喘吁吁,动弹不得了。

而且,因为MF的半实时性,导致GC在关闭窗口之后不能立即释放资源,如果用户反复打开关闭这些窗口,内存很快就溢出了。

所以实现一个窗口管理器非常重要,要确保每个时间只有一个窗口在运行。

10. 减少Timer的使用。

Timer也是性能消耗的大户,我曾见过一个程序里面打开了数十个Timer,那性能简直惨不忍睹。所以如果可能,保证整个程序只是用一个Timer,且只在必要的时候启动它,将会为你的程序减轻很多负担。

THE END