[转].NET 性能优化方法总结
来源:网络 编辑:admin
.NET 性能优化方法总结
Ver 1.0
2009-1-20
目录
1. C#语言方面... 4
1.1 垃圾回收... 4
1.1.1 避免不必要的对象创建... 4
1.1.2 不要使用空析构函数 ★... 4
1.1.3 实现 IDisposable 接口... 4
1.2 String 操作... 5
1.2.1 使用 StringBuilder 做字符串连接... 5
1.2.2 避免不必要的调用 ToUpper 或 ToLower 方法... 5
1.2.3 最快的空串比较方法... 6
1.3 多线程... 6
1.3.1 线程同步... 6
1.3.2 使用 ThreadStatic 替代 NameDataSlot ★... 7
1.3.3 多线程编程技巧... 7
1.4 类型系统... 8
1.4.1 避免无意义的变量初始化动作... 8
1.4.2 ValueType 和 ReferenceType. 8
1.4.3 尽可能使用最合适的类型... 9
1.5 异常处理... 10
1.5.1 不要吃掉异常★... 10
1.5.2 不要吃掉异常信息★... 10
1.5.3 避免不必要的抛出异常... 10
1.5.4 避免不必要的重新抛出异常... 10
1.5.5 捕获指定的异常,不要使用通用的System.Exception. 10
1.5.6 要在finally里释放占用的资源... 11
1.6 反射... 11
1.6.1 反射分类... 12
1.6.2 动态创建对象... 12
1.6.3 动态方法调用... 12
1.6.4 推荐的使用原则... 12
1.7 基本代码技巧... 13
1.7.1 循环写法... 13
1.7.2 拼装字符串... 13
1.7.3 避免两次检索集合元素... 13
1.7.4 避免两次类型转换... 14
1.7.5为字符串容器声明常量,不要直接把字符封装在双引号" "里面。... 14
1.7.6 用StringBuilder代替使用字符串连接符 “+”. 14
1.7.7 避免在循环体里声明变量,... 15
1.8 Hashtable. 15
1.8.1 Hashtable机理... 15
1.8.2 使用HashTale代替其他字典集合类型的情形:... 16
1.9 避免使用ArrayList。... 16
1.10从XML对象读取数据... 17
1.11 避免使用递归调用和嵌套循环,... 17
1.12 使用适当的Caching策略来提高性能... 17
2. Ado.Net 17
2.1 应用Ado.net的一些思考原则... 18
2.2 Connection. 18
2.2.1 在方法中打开和关闭连接... 18
2.2.2 显式关闭连接... 18
2.2.3 确保连接池启用... 19
2.2.4 不要缓存连接... 19
2.3 Command. 19
2.3.1 使用ExecuteScalar和ExecuteNonQuery. 19
2.3.2 使用Prepare. 19
2.3.3 使用绑定变量 ★... 19
2.4 DataReader 20
2.4.1 显式关闭DataReader 20
2.4.2 用索引号访问代替名称索引号访问属性... 20
2.4.3 使用类型化方法访问属性... 20
2.4.4 使用多数据集... 20
2.5 DataSet 21
2.5.1 利用索引加快查找行的效率... 21
2. 使用DataView.. 21
3.ASP.NET. 21
3.1 减少往返行程(Reduce Round Trips)... 21
3.2 避免阻塞和长时间的作业... 22
3.3 使用缓存... 22
3.4 多线程... 22
3.5 系统资源... 23
3.6 页面处理... 23
3.7 ViewState. 23
4.JScript 24
4.1 JScript性能优化的基本原则... 24
4.2 JScript语言本身的优化... 24
4.3 DOM相关... 27
4.4 其他... 28
1. C#语言方面
1.1 垃圾回收
垃圾回收解放了手工管理对象的工作,提高了程序的健壮性,但副作用就是程序代码可能对于对象创建变得随意。
1.1.1 避免不必要的对象创建
由于垃圾回收的代价较高,所以C#程序开发要遵循的一个基本原则就是避免不必要的对象创建。以下列举一些常见的情形。
1.1.1.1 避免循环创建对象 ★
如果对象并不会随每次循环而改变状态,那么在循环中反复创建对象将带来性能损耗。高效的做法是将对象提到循环外面创建。
1.1.1.2 在需要逻辑分支中创建对象
如果对象只在某些逻辑分支中才被用到,那么应只在该逻辑分支中创建对象。
1.1.1.3 使用常量避免创建对象
程序中不应出现如 new Decimal(0) 之类的代码,这会导致小对象频繁创建及回收,正确的做法是使用Decimal.Zero常量。我们有设计自己的类时,也可以学习这个设计手法,应用到类似的场景中。
1.1.1.4 使用StringBuilder做字符串连接
1.1.2 不要使用空析构函数 ★
如果类包含析构函数,由创建对象时会在 Finalize 队列中添加对象的引用,以保证当对象无法可达时,仍然可以调用到 Finalize 方法。垃圾回收器在运行期间,会启动一个低优先级的线程处理该队列。相比之下,没有析构函数的对象就没有这些消耗。如果析构函数为空,这个消耗就毫无意义,只会导致性能降低!因此,不要使用空的析构函数。
在实际情况中,许多曾在析构函数中包含处理代码,但后来因为种种原因被注释掉或者删除掉了,只留下一个空壳,此时应注意把析构函数本身注释掉或删除掉。
1.1.3 实现 IDisposable 接口
垃圾回收事实上只支持托管内在的回收,对于其他的非托管资源,例如 Window GDI 句柄或数据库连接,在析构函数中释放这些资源有很大问题。原因是垃圾回收依赖于内在紧张的情况,虽然数据库连接可能已濒临耗尽,但如果内存还很充足的话,垃圾回收是不会运行的。
C#的 IDisposable 接口是一种显式释放资源的机制。通过提供 using 语句,还简化了使用方式(编译器自动生成 try ... finally 块,并在 finally 块中调用 Dispose 方法)。对于申请非托管资源对象,应为其实现 IDisposable 接口,以保证资源一旦超出 using 语句范围,即得到及时释放。这对于构造健壮且性能优良的程序非常有意义!
为防止对象的 Dispose 方法不被调用的情况发生,一般还要提供析构函数,两者调用一个处理资源释放的公共方法。同时,Dispose 方法应调用 System.GC.SuppressFinalize(this),告诉垃圾回收器无需再处理 Finalize 方法了。
1.2 String 操作
1.2.1 使用 StringBuilder 做字符串连接
String 是不变类,使用 + 操作连接字符串将会导致创建一个新的字符串。如果字符串连接次数不是固定的,例如在一个循环中,则应该使用 StringBuilder 类来做字符串连接工作。因为 StringBuilder 内部有一个 StringBuffer ,连接操作不会每次分配新的字符串空间。只有当连接后的字符串超出 Buffer 大小时,才会申请新的 Buffer 空间。典型代码如下:
StringBuilder sb = new StringBuilder( 256 );
for ( int i = 0 ; i < Results.Count; i ++ )
{
sb.Append (Results[i]);
}
如果连接次数是固定的并且只有几次,此时应该直接用 + 号连接,保持程序简洁易读。实际上,编译器已经做了优化,会依据加号次数调用不同参数个数的 String.Concat 方法。例如:String str = str1 + str2 + str3 + str4;
会被编译为 String.Concat(str1, str2, str3, str4)。该方法内部会计算总的 String 长度,仅分配一次,并不会如通常想象的那样分配三次。作为一个经验值,当字符串连接操作达到 10 次以上时,则应该使用 StringBuilder。
这里有一个细节应注意:StringBuilder 内部 Buffer 的缺省值为 16 ,这个值实在太小。按 StringBuilder 的使用场景,Buffer 肯定得重新分配。经验值一般用 256 作为 Buffer 的初值。当然,如果能计算出最终生成字符串长度的话,则应该按这个值来设定 Buffer 的初值。使用 new StringBuilder(256) 就将 Buffer 的初始长度设为了256。
1.2.2 避免不必要的调用 ToUpper 或 ToLower 方法
String是不变类,调用ToUpper或ToLower方法都会导致创建一个新的字符串。如果被频繁调用,将导致频繁创建字符串对象。这违背了前面讲到的“避免频繁创建对象”这一基本原则。
例如,bool.Parse方法本身已经是忽略大小写的,调用时不要调用ToLower方法。
另一个非常普遍的场景是字符串比较。高效的做法是使用 Compare 方法,这个方法可以做大小写忽略的比较,并且不会创建新字符串。
例:
const string C_VALUE = "COMPARE";
if (String.Compare(sVariable, C_VALUE, true) == 0)
{
Console.Write("SAME");
}
还有一种情况是使用 HashTable 的时候,有时候无法保证传递 key 的大小写是否符合预期,往往会把 key 强制转换到大写或小写方法。实际上 HashTable 有不同的构造形式,完全支持采用忽略大小写的 key: new HashTable(StringComparer.OrdinalIgnoreCase)。
1.2.3 最快的空串比较方法
将String对象的Length属性与0比较是最快的方法:if (str.Length == 0)
其次是与String.Empty常量或空串比较:if (str == String.Empty)或if (str == "")
注:C#在编译时会将程序集中声明的所有字符串常量放到保留池中(intern pool),相同常量不会重复分配。
1.3 多线程
1.3.1 线程同步
线程同步是编写多线程程序需要首先考虑问题。C#为同步提供了 Monitor、Mutex、AutoResetEvent 和 ManualResetEvent 对象来分别包装 Win32 的临界区、互斥对象和事件对象这几种基础的同步机制。C#还提供了一个lock语句,方便使用,编译器会自动生成适当的 Monitor.Enter 和 Monitor.Exit 调用。
1.3.1.1 同步粒度
同步粒度可以是整个方法,也可以是方法中某一段代码。为方法指定 MethodImplOptions.Synchronized 属性将标记对整个方法同步。例如:
[MethodImpl(MethodImplOptions.Synchronized)]
public static SerialManager GetInstance()
{
if (instance == null )
{
instance = new SerialManager();
}
return instance;
}
通常情况下,应减小同步的范围,使系统获得更好的性能。简单将整个方法标记为同步不是一个好主意,除非能确定方法中的每个代码都需要受同步保护。
1.3.1.2 同步策略
使用 lock 进行同步,同步对象可以选择 Type、this 或为同步目的专门构造的成员变量。
避免锁定Type★
锁定Type对象会影响同一进程中所有AppDomain该类型的所有实例,这不仅可能导致严重的性能问题,还可能导致一些无法预期的行为。这是一个很不好的习惯。即便对于