C#NET性能优化总结.docx

上传人:b****3 文档编号:6314025 上传时间:2023-05-09 格式:DOCX 页数:16 大小:26.05KB
下载 相关 举报
C#NET性能优化总结.docx_第1页
第1页 / 共16页
C#NET性能优化总结.docx_第2页
第2页 / 共16页
C#NET性能优化总结.docx_第3页
第3页 / 共16页
C#NET性能优化总结.docx_第4页
第4页 / 共16页
C#NET性能优化总结.docx_第5页
第5页 / 共16页
C#NET性能优化总结.docx_第6页
第6页 / 共16页
C#NET性能优化总结.docx_第7页
第7页 / 共16页
C#NET性能优化总结.docx_第8页
第8页 / 共16页
C#NET性能优化总结.docx_第9页
第9页 / 共16页
C#NET性能优化总结.docx_第10页
第10页 / 共16页
C#NET性能优化总结.docx_第11页
第11页 / 共16页
C#NET性能优化总结.docx_第12页
第12页 / 共16页
C#NET性能优化总结.docx_第13页
第13页 / 共16页
C#NET性能优化总结.docx_第14页
第14页 / 共16页
C#NET性能优化总结.docx_第15页
第15页 / 共16页
C#NET性能优化总结.docx_第16页
第16页 / 共16页
亲,该文档总共16页,全部预览完了,如果喜欢就下载吧!
下载资源
资源描述

C#NET性能优化总结.docx

《C#NET性能优化总结.docx》由会员分享,可在线阅读,更多相关《C#NET性能优化总结.docx(16页珍藏版)》请在冰点文库上搜索。

C#NET性能优化总结.docx

C#NET性能优化总结

C#.NET性能优化总结

RevisionHistory

Date

Revision

Description

Author

Approvedby

2009.2.6

1.0.0.1

起草

尤西锋

目录

1.垃圾回收2

避免不必要的对象创建3

不要使用空析构函数3

实现Idisposable接口3

2.String操作4

使用StringBuilder做字符串连接4

避免不必要的ToUpper或ToLower方法5

最快的空串比较方法5

3.类型系统6

避免无意义的变量初始化6

ValueType和ReferenceType6

尽可能使用最合适的类型8

4.异常处理8

不要吃掉异常8

不要吃掉异常信息8

避免不必要的抛出异常8

避免不必要的重新抛出异常9

捕获指定的异常不要使用通用的System.Exception9

要在finally里释放占用的资源10

5.反射10

反射分类10

动态创建对象11

动态方法调用11

推荐的使用原则11

6.基本代码技巧12

循环写法12

拼装字符串12

避免两次检索集合元素13

避免两次类型转换14

为字符串容器声明常量14

使用StringBuilder15

避免在循环体里声明变量15

7.Hashtable16

Hashtable机制16

使用HashTale代替其他字典集合类型的情形17

8.避免使用ArrayList17

9.从XML对象读取数据18

垃圾回收

垃圾回收解放了手工管理对象的工作,提高了程序的健壮性,但副作用就是程序代码可能对于对象创建变得随意。

避免不必要的对象创建

由于垃圾回收的代价较高,所以C#程序开发要遵循的一个基本原则就是避免不必要的对象创建。

以下列举一些常见的情形。

1、避免循环创建对象。

如果对象并不会随每次循环而改变状态,那么在循环中反复创建对象将带来性能损耗。

高效的做法是将对象提到循环外面创建。

2、在需要逻辑分支中创建对象。

如果对象只在某些逻辑分支中才被用到,那么应只在该逻辑分支中创建对象。

3、使用常量避免创建对象。

程序中不应出现如newDecimal(0)之类的代码,这会导致小对象频繁创建及回收,正确的做法是使用Decimal.Zero常量。

我们有设计自己的类时,也可以学习这个设计手法,应用到类似的场景中。

4、使用StringBuilder做字符串连接。

不要使用空析构函数

如果类包含析构函数,由创建对象时会在Finalize队列中添加对象的引用,以保证当对象无法可达时,仍然可以调用到Finalize方法。

垃圾回收器在运行期间,会启动一个低优先级的线程处理该队列。

相比之下,没有析构函数的对象就没有这些消耗。

如果析构函数为空,这个消耗就毫无意义,只会导致性能降低。

因此,不要使用空的析构函数。

在实际情况中,许多曾在析构函数中包含处理代码,但后来因为种种原因被注释掉或者删除掉了,只留下一个空壳,此时应注意把析构函数本身注释掉或删除掉。

实现Idisposable接口

垃圾回收事实上只支持托管内在的回收,对于其他的非托管资源,例如WindowGDI句柄或数据库连接,在析构函数中释放这些资源有很大问题。

原因是垃圾回收依赖于内存在紧张的情况,虽然数据库连接可能已濒临耗尽,但如果内存还很充足的话,垃圾回收是不会运行的。

C#的IDisposable接口是一种显式释放资源的机制。

通过提供using语句,还简化了使用方式(编译器自动生成try...finally块,并在finally块中调用Dispose方法)。

对于申请非托管资源对象,应为其实现IDisposable接口,以保证资源一旦超出using语句范围,即得到及时释放。

这对于构造健壮且性能优良的程序非常有意义。

为防止对象的Dispose方法不被调用的情况发生,一般还要提供析构函数,两者调用一个处理资源释放的公共方法。

同时,Dispose方法应调用System.GC.SuppressFinalize(this),告诉垃圾回收器无需再处理Finalize方法了。

String操作

使用StringBuilder做字符串连接

String是不变类,使用+操作连接字符串将会导致创建一个新的字符串。

如果字符串连接次数不是固定的,例如在一个循环中,则应该使用StringBuilder类来做字符串连接工作。

因为StringBuilder内部有一个StringBuffer,连接操作不会每次分配新的字符串空间。

只有当连接后的字符串超出Buffer大小时,才会申请新的Buffer空间。

典型代码如下:

StringBuildersb=newStringBuilder(256);

for(inti=0;i

{

sb.Append(Results[i]);

}

如果连接次数是固定的并且只有几次,此时应该直接用+号连接,保持程序简洁易读。

实际上,编译器已经做了优化,会依据加号次数调用不同参数个数的String.Concat方法。

例如:

Stringstr=str1+str2+str3+str4;会被编译为String.Concat(str1,str2,str3,str4)。

该方法内部会计算总的String长度,仅分配一次,并不会如通常想象的那样分配三次。

作为一个经验值,当字符串连接操作达到10次以上时,则应该使用StringBuilder。

这里有一个细节应注意:

StringBuilder内部Buffe的缺省值为16,这个值实在太小。

按StringBuilder的使用场景,Buffer肯定得重新分配。

经验值一般用256作为Buffer的初值。

当然,如果能计算出最终生成字符串长度的话,则应该按这个值来设定Buffer的初值。

使用newStringBuilder(256)就将Buffer的初始长度设为了256。

避免不必要的ToUpper或ToLower方法

String是不变类,调用ToUpper或ToLower方法都会导致创建一个新的字符串。

如果被频繁调用,将导致频繁创建字符串对象。

这违背了前面讲到的“避免频繁创建对象”这一基本原则。

例如,bool.Parse方法本身已经是忽略大小写的,调用时不要调用ToLower方法。

另一个非常普遍的场景是字符串比较。

高效的做法是使用Compare方法,这个方法可以做大小写忽略的比较,并且不会创建新字符串。

例:

conststringC_VALUE="COMPARE";

if(String.Compare(sVariable,C_VALUE,true)==0)

{

Console.Write("SAME");

}

还有一种情况是使用HashTable的时候,有时候无法保证传递key的大小写是否符合预期,往往会把key强制转换到大写或小写方法。

实际上HashTable有不同的构造形式,完全支持采用忽略大小写的key:

newHashTable(StringComparer.OrdinalIgnoreCase)。

最快的空串比较方法

 将String对象的Length属性与0比较是最快的方法:

if(str.Length==0)

 其次是与String.Empty常量或空串比较:

if(str==String.Empty)或if(str=="")

 注:

C#在编译时会将程序集中声明的所有字符串常量放到保留池中(internpool),相同常量不会重复分配。

类型系统

避免无意义的变量初始化

CLR保证所有对象在访问前已初始化,其做法是将分配的内存清零。

因此,不需要将变量重新初始化为0、false或null。

需要注意的是:

方法中的局部变量不是从堆而是从栈上分配,所以C#不会做清零工作。

如果使用了未赋值的局部变量,编译期间即会报警。

不要因为有这个印象而对所有类的成员变量也做赋值动作,两者的机理完全不同。

ValueType和ReferenceType

以引用方式传递值类型参数

值类型从调用栈分配,引用类型从托管堆分配。

当值类型用作方法参数时,默认会进行参数值复制,这抵消了值类型分配效率上的优势。

作为一项基本技巧,以引用方式传递值类型参数可以提高性能。

为ValueType提供Equals方法

.NET默认实现的ValueType.Equals方法使用了反射技术,依靠反射来获得所有成员变量值做比较,这个效率极低。

如果我们编写的值对象其Equals方法要被用到(例如将值对象放到HashTable中),那么就应该重载Equals方法。

publicstructRectangle

{

publicdoubleLength;

publicdoubleBreadth;

publicoverrideboolEquals(objectob)

{

if(obisRectangle)

returnEquels((Rectangle)ob))

else

returnfalse;

}

privateboolEquals(Rectanglerect)

{

returnthis.Length==rect.Length&&this.Breadth==rect.Breach;

}

}

避免装箱和拆箱

C#可以在值类型和引用类型之间自动转换,方法是装箱和拆箱。

装箱需要从堆上分配对象并拷贝值,有一定性能消耗。

如果这一过程发生在循环中或是作为底层方法被频繁调用,则应该警惕累计的效应。

一种经常的情形出现在使用集合类型时。

例如:

//避免如下操作

ArrayListal=newArrayList();

for(inti=0;i<1000;i++)

{

//装箱操作

al.Add(i);

}

//拆箱操作

intf=(int)al[0];

但是得当心,如果你像使用引用类型那么频繁的使用一个值类型的话,值类型的优势会很快被耗尽。

比如,把一个值类型压到一个含有对象类型的群集。

这叫做装箱,很耗用处理器周期,尤其是当你的代码在把它作为值(对它进行数学运算)和把它作为引用之间来回运行时。

尽可能使用最合适的类型

尽可能使用最合适的类型来描述数据,从而减少类型转换。

使用泛型来创建群集和其它的数据结构,这样,在运行时,它们就可以被实例化来存储刚好合适的类型。

这节省了装箱/拆箱和类型转换的时间。

在C#中使用as,而不是is。

关键字is用来查看引用是否可以被作为某个具体的类型,但是并不返回转换到这个类型的引用。

所以,通常当你从is获得一个正的结果时,你首先应该cast——有效地执行两次cast。

采用as关键词时,如果可用,则返回cast为新类型的引用;否则返回null。

你可以查看null然后做你喜欢做的事情。

整体来说,as方法要比is方法快50%。

异常处理

不要吃掉异常

关于异常处理的最重要原则就是:

不要吃掉异常。

这个问题与性能无关,但对于编写健壮和易于排错的程序非常重要。

这个原则换一种说法,就是不要捕获那些你不能处理的异常。

吃掉异常是极不好的习惯,因为你消除了解决问题的线索。

一旦出现错误,定位问题将非常困难。

除了这种完全吃掉异常的方式外,只将异常信息写入日志文件但并不做更多处理的做法也同样不妥。

不要吃掉异常信息

有些代码虽然抛出了异常,但却把异常信息吃掉了。

为异常披露详尽的信息是程序员的职责所在。

如果不能在保留原始异常信息含义的前提下附加更丰富和更人性化的内容,那么让原始的异常信息直接展示也要强得多。

千万不要吃掉异常信息。

避免不必要的抛出异常

抛出异常和捕获异常属于消耗比较大的操作,在可能的情况下,应通过完善程序逻辑避免抛出不必要的异常。

与此相关的一个倾向是利用异常来控制处理逻辑。

尽管对于极少数的情况,这可能获得更为优雅的解决方案,但通常而言应该避免。

避免不必要的重新抛出异常

如果是为了包装异常的目的(即加入更多信息后包装成新异常),那么是合理的。

但是有不少代码,捕获异常没有做任何处理就再次抛出,这将无谓地增加一次捕获异常和抛出异常的消耗,对性能有伤害。

捕获指定的异常不要使用通用的System.Exception

//避免

try

{

}

catch(Exceptionexc)

{

}

//推荐

try

{

}

catch(System.NullReferenceExceptionexc)

{

}

catch(System.ArgumentOutOfRangeExceptionexc)

{

}

catch(System.InvalidCastExceptionexc)

{

}

要在finally里释放占用的资源

使用Try...catch...finally时,要在finally里释放占用的资源如:

连接,文件流等,不然在Catch到错误后占用的资源不能释放。

 

反射

反射是一项很基础的技术,它将编译期间的静态绑定转换为延迟到运行期间的动态绑定。

在很多场景下(特别是类框架的设计),可以获得灵活易于扩展的架构。

但带来的问题是与静态绑定相比,动态绑定会对性能造成较大的伤害。

反射分类

typecomparison:

类型判断,主要包括is和typeof两个操作符及对象实例上的GetType调用。

这是最轻型的消耗,可以无需考虑优化问题。

注意typeof运算符比对象实例上的GetType方法要快,只要可能则优先使用typeof运算符。

memberenumeration:

成员枚举,用于访问反射相关的元数据信息,例如Assembly.GetModule、Module.GetType、Type对象上的IsInterface、IsPublic、GetMethod、GetMethods、GetProperty、GetProperties、GetConstructor调用等。

尽管元数据都会被CLR缓存,但部分方法的调用消耗仍非常大,不过这类方法调用频度不会很高,所以总体看性能损失程度中等。

memberinvocation:

成员调用,包括动态创建对象及动态调用对象方法,主要有Activator.CreateInstance、Type.InvokeMember等。

动态创建对象

 C#主要支持5种动态创建对象的方式:

1.Type.InvokeMember

2.ContructorInfo.Invoke

3.Activator.CreateInstance(Type)

4.Activator.CreateInstance(assemblyName,typeName)

5.Assembly.CreateInstance(typeName)

最快的是方式3,与DirectCreate的差异在一个数量级之内,约慢7倍的水平。

其他方式,至少在40倍以上,最慢的是方式4,要慢三个数量级。

动态方法调用

方法调用分为编译期的早期绑定和运行期的动态绑定两种,称为Early-BoundInvocation和Late-BoundInvocation。

Early-BoundInvocation可细分为Direct-call、Interface-call和Delegate-call。

Late-BoundInvocation主要有Type.InvokeMember和MethodBase.Invoke,还可以通过使用LCG(LightweightCodeGeneration)技术生成IL代码来实现动态调用。

从测试结果看,相比DirectCall,Type.InvokeMember要接近慢三个数量级;MethodBase.Invoke虽然比Type.InvokeMember要快三倍,但比DirectCall仍慢270倍左右。

可见动态方法调用的性能是非常低下的。

我们的建议是:

除非要满足特定的需求,否则不要使用。

推荐的使用原则

  1.如果可能,则避免使用反射和动态绑定

  2.使用接口调用方式将动态绑定改造为早期绑定

  3.使用Activator.CreateInstance(Type)方式动态创建对象

  4.使用typeof操作符代替GetType调用

  5.在已获得Type的情况下,使用Assembly.CreateInstance(type.FullName)。

基本代码技巧

这里描述一些应用场景下,可以提高性能的基本代码技巧。

对处于关键路径的代码,进行这类的优化还是很有意义的。

普通代码可以不做要求,但养成一种好的习惯也是有意义的。

循环写法

可以把循环的判断条件用局部变量记录下来。

局部变量往往被编译器优化为直接使用寄存器,相对于普通从堆或栈中分配的变量速度快。

如果访问的是复杂计算属性的话,提升效果将更明显。

for(inti=0,j=collection.GetIndexOf(item);i

需要说明的是:

这种写法对于CLR集合类的Count属性没有意义,原因是编译器已经按这种方式做了特别的优化。

拼装字符串

拼装好之后再删除是很低效的写法。

有些方法其循环长度在大部分情况下为1,这种写法的低效就更为明显了:

//避免

publicstaticstringToString(MetadataKeyentityKey)

{

stringstr="";

object[]vals=entityKey.values;

for(inti=0;i

{

str+=","+vals[i].ToString();

}

returnstr==""?

"":

str.Remove(0,1);

}

推荐下面的写法:

//推荐

if(str.Length==0)

str=vals[i].ToString();

else

str+=","+vals[i].ToString();

其实这种写法非常自然,而且效率很高,完全不需要用个Remove方法绕来绕去。

避免两次检索集合元素

获取集合元素时,有时需要检查元素是否存在。

通常的做法是先调用ContainsKey(或Contains)方法,然后再获取集合元素。

这种写法非常符合逻辑。

但如果考虑效率,可以先直接获取对象,然后判断对象是否为null来确定元素是否存在。

对于Hashtable,这可以节省一次GetHashCode调用和n次Equals比较。

如下面的示例:

//避免

publicIDataGetItemByID(Guidid)

{

IDatadata1=null;

if(this.idTable.ContainsKey(id.ToString())

{

data1=this.idTable[id.ToString()]asIData;

}

returndata1;

}

其实完全可用一行代码完成:

returnthis.idTable[id]asIData;

避免两次类型转换

考虑如下示例,其中包含了两处类型转换:

//避免

if(objisSomeType)

{

SomeTypest=(SomeType)obj;

st.SomeTypeMethod();

}

效率更高的做法如下:

//推荐

SomeTypest=objasSomeType;

if(st!

=null)

{

st.SomeTypeMethod();

}

为字符串容器声明常量

为字符串容器申明变量,不要直接把字符封装在双引号“”里面。

//避免

MyObjectobj=newMyObject();

obj.Status="ACTIVE";

//推荐

conststringC_STATUS="ACTIVE";

MyObjectobj=newMyObject();

obj.Status=C_STATUS;

使用StringBuilder

用StringBuilder代替使用字符串连接符“+”

//避免

StringsXML="";

sXML+="";

sXML+="Data";

sXML+="";

sXML+="";

//推荐

StringBuildersbXML=newStringBuilder();

sbXML.Append("");

sbXML.Append("");

sbXML.Append("Data");

sbXML.Append("");

sbXML.Append("");

避免在循环体里声明变量

应该在循环体外声明变量,在循环体里初始化。

//避免

for(inti=0;i<10;i++)

{

  SomeClassobjSC=newSomeClass();

}

//推荐

SomeClassobjSC=null;

for(inti=0;i<10;i++)

{

objSC=newSomeClass();

Hashtable

Hashtable机制

Hashtable是一种使用非常频繁的基础集合类型。

需要理解影响Hashtable的效率有两个因素:

一是散列码(GetHashCode方法),二是等值比较(Equals方法)。

Hashtable首先使用键的散列码将对象分布到不同的存储桶中,随后在该特定的存储桶中使用键的Equals方法进行查找。

良好的散列码是第一位的因素,最理想的情况是每个不同的键都有不同的散列码。

Equals方法也很重要,因为散列只需要做一次,而存储桶中查找键可能需要做多次。

从实际经验看,使用Hashtable时,Equals方法的消耗一般会占到一半以上。

System.Object类提供了默认的GetHashCode实现,使用对象在内存中的地址作为散列码。

我们遇到过一个用Hashtable来缓存对象的例子,每次根据传递的OQL表达式构造出一个ExpressionList对象,再调用QueryCompiler的方法编译得到CompiledQuery对象。

以ExpressionList对象和CompiledQuery对象作为键值对存储到Hashtable中。

ExpressionList对象没有重载GetHashCode实现,其超类ArrayList也没有,这样最后用的就是System.Object类的GetHashCode实现。

由于ExpressionList对象会每次构造,因此它的HashCode每次都不同,所以这个CompiledQueryCac

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > 工程科技 > 能源化工

copyright@ 2008-2023 冰点文库 网站版权所有

经营许可证编号:鄂ICP备19020893号-2