现在的位置: 首页 > 综合 > 正文

浅谈 .net 运行效率

2013年09月20日 ⁄ 综合 ⁄ 共 3224字 ⁄ 字号 评论关闭

  .net 执行效率的问题是长期以来比较热门的话题,主要围绕 垃圾回收器(GC)、托管代码、JIT(Just In-Time) 等为中心讨论。

  首先说GC,事实上 .net 中所使用的 GC 是一个现代化的、高效率的 GC,同 LISP 的 GC 已经大不一样,他使用一种相当复杂的算法。使用 GC 可以使程序员轻松许多,把更多的精力放在应用程序的主要逻辑上,而不用为内存的分配和释放消耗时间,当然 GC 对性能的影响还是不能忽视的。GC 运行时,其他工作都会停止,不过幸好他不会经常运行。

  可爱的 JIT,他是在应用程序第一次执行时把 IL 编译成为本机代码,这当然要消耗 CPU时 间,但他带来的优点要比他的缺点多得多。JIT 并不是一次把所有代码全都编译为本机代码,而是当程序调用哪个方法(过程),JIT 才会把那个方法编译为本机代码。JIT 还针对不同的平台进行优化,这是传统编译器所不能达到的。理论上,不同的 JIT 就可以实现 .net 程序跨平台运行。例如:Mono(一个在 Linux 上实现 .net 框架的项目,参见 http://www.go-mono.com/,截稿时最新版为 0.3)。

  不过跨平台性并不是想象中那么好,从 Java的 “一次编译,到处运行”被嬉称为“一次编译,到处调试”就可以看出。这是由于操作系统的巨大差异造成的。所以,跨平台的应用程序要有针对性的对不同平台进行优化,可以有效提高程序的稳定性和效率。

  不管怎么样,Microsoft 已经把 JIT 做的很好了。至少目前的 CLR 1.1 要比 JVM 1.4 快很多。或许名为 Tiger(猛虎) 的 1.5 版 JVM 会大幅提升效率。不过,运行时编译的性能在企业级应用中的重要性并不很高,所以也不要过分在意他。

  另外,还有一些提升性能的技巧:

 

一、String 和 StringBuilder

  当一个字符串需要经常改变时,应考虑使用 StringBuilder。虽然 StringBuilder 所提供的功能较少。一个折中的办法是,在必要时通过 StringBuilder 的 ToString() 方法转换为 String(注意:.net 1.1中,无论显式或者隐式转换都不能把 StringBuilder 转换为 String。仅仅可以通过调用 StringBuilder 的 ToString() 方法实现。)。

  String 实际上是一个不可改变的数据类型,他在托管堆中分配的内存刚好满足字符串长度。修改字符串实际上是创建一个新的字符串,必要时把旧的字符串复制到新的字符串中。显然,当不断添加字符串内容时,程序变得效率低下,而且旧字符串变成了垃圾,促使 GC 运行次数增加。而 StringBuilder 通常分配的内存比需要的多。可以在 StringBuilder 的一个构造函数重载中显式指定这一长度。只有当扩展字符串长度时,才会重新分配需要的内存。新的长度如果没有显式指定,大约是旧长度的2倍(注意:这一点 Microsoft 并没有提供明确的文档说明,2倍是通过观察内存变化得出的。)。

  由此可见,使用 StringBuilder 可以大幅减少内存重分配、复制的次数和垃圾的数量。但这仅仅适用于追加子字符串和替换字符,删除和插入子字符串仍然效率低下。

  还有一点需要注意,使用 StringBuilder 需要引用 System.Text 命名空间。

  对于数组 Array 和 ArrayList 类似于 String 和 StringBuilder。

 

二、DataReader 的索引器

  DataReader 具有两种索引器,一种是 int 类型,一种是 string 类型。两种索引器采用了不同的算法。进行简单的迭代测试,使用 int 的索引器大约比 string 的索引器快5倍以上。这当然不准确,但至少说明了 int 索引器效率更高。所以,建议使用 int 索引器。不过,使用 string 索引器的程序具有更高的可读性。有一个好办法是使用常量来定义 int 索引器的索引。例如:

private const int FIELD_TABLE1_ID = 0;
// ....
Response.Write(dataReader[FIELD_TABLE1_ID].ToString());

  还有注意,当改变 SELECT 查询时,不要忘记检查常量是否仍正确对应字段。

 

三、DataReader 和 DataSet

  DataReader 是一种只读的向前流,DataSet 是具有复杂结构的数据集。填充 DataReader 要比填充 DataSet 快很多。(DataSet 是用 DataReader 填充的)但是 DataSet 是断开连接的,而 DataReader 会使数据库连接一直处于打开状态。数据库连接是一种昂贵的资源,所以,要尽量使用 DataSet 或者派生于 DataSet 的强类型 DataSet。还有一点,在传递数据集时(例如在层之间传递,WebService 发送,接受数据集)一定不要使用 DataReader。

  在下列情况使用 DataReader

  • 对数据集的简单迭代
  • 具有简单数据结构的数据集的快速访问
  • 对简单控件进行数据绑定

  迭代DataReader时,尽量避免在迭代过程中实现过多的逻辑。

 

四、高成本资源的使用

  前面说过,数据库连接就是一种高成本资源。使用时要尽可能晚的建立,并且尽可能早的关闭。使用 C# 中的 try…catch…finally 块可以确保在 finally 中关闭数据库连接,但有时可能会忘记编写 connection.Close() 这样的代码。另一种方法是使用 using 语句,例如:

using (SqlConnection connection = new SqlConnection(connectionString))
{
   connection.Open();
   //…
}

在出作用域后,CLR 会自动调用 Dispose() 方法。(using 语句中的对象必须实现 IDisposable 接口)这种方法可以确保执行 Close() 方法,但不利于异常处理。综合两种方法,可以在 try 中嵌套 using,例如:

try
{
   using (SqlConnection connection = new SqlConnection(connectionString))
   {
      connection.Open();
      //…
   }
}
catch (Exception ex)
{
   //…
}
finally
{
   //…
}

  另外,在处理多层应用程序中的 DataAccess 层时,应使类实现 IDisposable 接口。

 

五、指针

  一个老生常谈的问题,请记住:你是在 .net 提供的托管环境中编程,所以如果不是非常必要和有充足的理由,就请不要使用指针。在 c++ 中,指针非常灵活的,但也非常危险。在 .net 中,指针的部分功能可以找到替代品,比如函数指针可以用委托代替。使用委托的一个优点是委托的类型安全性非常高。但是,有些任务不可避免要用到指针,比如说编写调试器。

  当你使用指针时,CLR 认为你明确知道自己在干什么。因为指针会绕过一切运行时的安全性检查。

  还有一点,包含不安全代码的程序(使用指针就是其中一种)可能会被禁止运行(最终的决定权在用户手中)。

 

六、其它补充

  在 .net 中,你可以随心所欲的使用异常机制,当然,要注意处理层次。他不会对性能造成大的影响。这和 c++ 不一样,所以 c++ 程序员可以放心使用。

  注意后期绑定,要尽量避免使用这种技术。

 

 

参考书目及网站:

《C# 高级编程》清华大学出版社

《ASP.NET高级编程》清华大学出版社

《.net Framework高级编程》清华大学出版社

《ADO.NET高级编程》中国电力出版社

《.net 程序设计核心编程》清华大学出版社

MSDN (http://www.microsoft.com/msdn)

CSDN (http://www.csdn.net)

开发者俱乐部 (http://www.dev-club.com)

抱歉!评论已关闭.