总结使用Unity3D优化游戏运行性能的经验

时间:2019-05-12 16:46:05下载本文作者:会员上传
简介:写写帮文库小编为你整理了多篇相关的《总结使用Unity3D优化游戏运行性能的经验》,但愿对你工作学习有帮助,当然你在写写帮文库还可以找到更多《总结使用Unity3D优化游戏运行性能的经验》。

第一篇:总结使用Unity3D优化游戏运行性能的经验

流畅的游戏玩法来自流畅的帧率,而我们即将推出的动作平台游戏《Shadow Blade》已经将在标准iPhone和iPad设备上实现每秒60帧视为一个重要目标。

以下是我们在紧凑的优化过程中提升游戏运行性能,并实现目标帧率时需要考虑的事项。--来自狗刨学习网

当基本游戏功能到位时,就要确保游戏运行表现能够达标。我们衡量游戏运行表现的一个基本工具是Unity内置分析器以及Xcode分析工具。使用Unity分析器来分析设备上的运行代码真是一项宝贵的功能。

我们总结了这种为将目标设备的帧率控制在60fps而进行衡量、调整、再衡量过程的中相关经

一、遇到麻烦时要调用“垃圾回收器”(Garbage Collector,无用单元收集程序,以下简称GC)

由于具有C/C++游戏编程背景,我们并不习惯无用单元收集程序的特定行为。确保自动清理你不用的内存,这种做法在刚开始时很好,但很快你就公发现自己的分析器经常显示CPU负荷过大,原因是垃圾回收器正在收集垃圾内存。这对移动设备来说尤其是个大问题。要跟进内存分配,并尽量避免它们成为优先数,以下是我们应该采取的主要操作:

1.移除代码中的任何字符串连接,因为这会给GC留下大量垃圾。

2.用简单的“for”循环代替“foreach”循环。由于某些原因,每个“foreach”循环的每次迭代会生成24字节的垃圾内存。一个简单的循环迭代10次就可以留下240字节的垃圾内存。

3.更改我们检查游戏对象标签的方法。用“if(go.CompareTag(“Enemy”)”来代替“if(go.tag == “Enemy”)”。在一个内部循环调用对象分配的标签属性以及拷贝额外内存,这是一个非常糟糕的做法。

4.对象库很棒,我们为所有动态游戏对象制作和使用库,这样在游戏运行时间内不会动态分配任何东西,不需要的时候所有东西反向循环到库中。

5.不使用LINQ命令,因为它们一般会分配中间缓器,而这很容易生成垃圾内存。

二、谨慎处理高级脚本和本地引擎C++代码之间的通信开销。

所有使用Unity3D编写的游戏玩法代码都是脚本代码,在我们的项目中是使用Mono执行时间处理的C#代码。任何与引擎数据的通信需求都要有一个进入高级脚本语言的本地引擎代码的调用。这当然会产生它自己的开销,而尽量减少游戏代码中的这些调用则要排在第二位。

1.在这一情景中四处移动对象要求来自脚本代码的调用进入引擎代码,这样我们就会在游戏玩法代码的一个帧中缓存某一对象的转换需求,并一次仅向引擎发送一个请求,以便减少调用开销。这种模式也适用于其他相似的地方,而不仅局限于移动和旋转对象。

2.将引用本地缓存到元件中会减少每次在一个游戏对象中使用 “GetComponent” 获取一个元件引用的需求,这是调用本地引擎代码的另一个例子。

三、物理效果

1.将物理模拟时间步设置到最小化状态。在我们的项目中就不可以将让它低于16毫秒。

2.减少角色控制器移动命令的调用。移动角色控制器会同步发生,每次调用都会耗损极大的性能。我们的做法是缓存每帧的移动请求,并且仅运用一次。

3.修改代码以免依赖“ControllerColliderHit” 回调函数。这证明这些回调函数处理得并不十分迅速。

4.面对性能更弱的设备,要用skinned mesh代替physics cloth。cloth参数在运行表现中发挥重要作用,如果你肯花些时间找到美学与运行表现之间的平衡点,就可以获得理想的结果。

5.在物理模拟过程中不要使用ragdolls,只有在必要时才让它生效。

6.要谨慎评估触发器的“onInside”回调函数,在我们的项目中,我们尽量在不依赖它们的情况下模拟逻辑。

7.使用层次而不是标签。我们可以轻松为对象分配层次和标签,并查询特定对象,但是涉及碰撞逻辑时,层次至少在运行表现上会更有明显优势。更快的物理计算和更少的无用分配内存是使用层次的基本原因。

8.千万不要使用Mesh对撞机。

9.最小化碰撞检测请求(例如ray casts和sphere checks),尽量从每次检查中获得更多信息。

四、让AI代码更迅速

我们使用AI敌人来阻拦忍者英雄,并同其过招。以下是与AI性能问题有关的一些建议:

1.AI逻辑(例如能见度检查等)会生成大量物理查询。可以让AI更新循环设置低于图像更新循环,以减少CPU负荷。

五、最佳性能表现根本就不是来自代码!

没有发生什么情况的时候,就说明性能良好。这是我们关闭一切不必要之物的基本原则。我们的项目是一个侧边横向卷轴动作游戏,所以如果不具有可视性时,就可以关闭许多动态关卡物体。

1.使用细节层次的定制关卡将远处的敌人AI关闭。

2.移动平台和障碍,当它们远去时其物理碰撞机也会关闭。

3.Unity内置的“动画挑选”系统可以用来关闭未被渲染对象的动画。

4.所有关卡内的粒子系统也可以使用同样的禁用机制。

六、回调函数!那么空白的回调函数呢?

要尽量减少Unity回调函数。即使敌人回调函数存在性能损失。没有必要将空白的回调函数留在代码库中(有时候介于大量代码重写和重构之间)。

七、让美术人员来救场

在程序员抓耳挠腮,绞尽脑汁去想该如何让每秒运行更多帧时,美术人员总能神奇地派上大用场。

1.共享游戏对象材料,令其在Unity中处于静止状态,可以让它们绑定在一起,由此产生的简化绘图调用是呈现良好移动运行性能的重要元素。

2.纹理地图集对UI元素来说尤其有用。

3.方形纹理以及两者功率的合理压缩是必不可少的步骤。

4.我们的美术人员移除了所有远处背景的网格,并将其转化为简单的2D位面。

5.光照图非常有价值。

6.我们的美术人员在一些关口移除了额外顶点。

7.使用合理的纹理mip标准是一个好主意(注:要让不同分辨率的设备呈现良好的帧率时尤其如此)。

8.结合网格是美术人员可以发挥作用的另一个操作。

9.我们的动画师尽力让不同角色共享动画。

10.要找到美学/性能之间的平衡,就免不了许多粒子效果的迭代。减少发射器数量并尽量减少透明度需求也是一大挑战。

八、要减少内存使用

使用大内存当然会对性能产生负面影响,但在我们的项目中,我们的iPod由于超过内存上限而遭遇了多次崩溃事件。我们的游戏中最耗内存的是纹理。

1.不同设备要使用不同的纹理大小,尤其是UI和大型背景中的纹理。《Shadow Blade》使用的是通用型模板,但如果在启动时检测到设备大小和分辨率,就会载入不同资产。

2.我们要确保未使用的资产不会载入内存。我们必须迟一点在项目中找到仅被一个预制件实例引用,并且从未完全载入内存中实例化的资产。

3.去除网格中的额外多边形也能实现这一点。

4.我们应该重建一些资产的生周期管理。例如,调整主菜单资产的加载/卸载时间,或者关卡资产、游戏音乐的有效期限。

5.每个关卡都要有根据其动态对象需求而量身定制的特定对象库,并根据最小内存需求来优化。对象库可以灵活一点,在开发过程中包含大量对象,但知道游戏对象需求后就要具体一点。

6.保持声音文件在内存的压缩状态也是必要之举。

加强游戏运行性能是一个漫长而具有挑战性的过程,游戏开发社区所分享的大量知识,以及Unity提供的出色分析工具为《Shadow Blade》实现目标运行性能提供了极大帮助。

第二篇:计算机系统性能优化总结

计算机系统性能优化总结

现今,计算机技术在社会各行各业都得到了广泛的应用。计算机给我们的学习、生活和工作都带来了极大的便利。但随着我们对计算机整体性能要求的提高,计算机系统性能的优化就显得尤为重要。

一、计算机系统运行不佳的原因分析

计算机系统运行性能不佳的原因有很多。如,系统平台结构不好、系统配置不好或参数设置不对;应用系统数据结构设计不合理,加大了系统的输入和输出需求;应用系统算法或逻辑处理有问题,使计算机系统达不到最佳的运行状态。

二、计算机系统性能优化措施

1.合理地配置各种软件,使计算机系统发挥最好的功能。计算机系统由硬件系统和软件系统组成,二者之间相互依赖,这就要求我们在使用计算机软件的过程中,使用一些速度较快、版本较高和功能较完善的软件,并仔细阅读各种软件的使用说明,避免在应用过程中发生冲突。作为编程人员,在编写应用程序的过程中,要充分考虑应用系统数据结构设计的合理性,以便使计算机系统达到最佳的运行状态。

2.调整输入和输出系统。在计算机系统的应用过程中,我们进行的大多数操作就是输入和输出。因此,输入和输出操作是影响计算机性能的一个重要因素。随着科技的日益发

展,磁盘的平均寻址时间日益缩短,但与中央处理器的运算相比,仍然缓慢很多。在观察一些系统运行时,经常出现中央处理器处在空闲状态而应用程序却迟迟不能完成的情况。究其原因,就是因为磁盘的输入和输出的速度太慢,数据没有读(写)入内存中。因此,在实际的应用过程中,我们可以考虑把数据文件存放在不同的磁盘上,让多个磁盘并行工作,从而解决输入和输出的瓶颈问题。如果输入和输出总数明显不合理,就要考虑查找引起输入和输出数量增大的原因,从而优化应用程序,减少输入和输出的次数,提高系统的性能。

3.安排相同性质的处理过程同时运行,以确保中央处理器和输入和输出的绝对通畅。一台计算机能够同时运行多个应用程序,从使用系统资源的角度来看,这些应用程序可以分为面向输入和输出与面向运算2种类型。

系统中如果有2个或多个面向输入和输出的应用在同时运行,就会造成中央处理器闲置而大量磁盘输入和输出拥塞和等待的情况,使得各个应用程序的性能变差。系统中如果有2个或多个面向运算的应用程序同时运行时,就会造成磁盘空转的情况。因此,要尽量避免让多个面向输入和输出或多个面向运算的应用程序同时运行。最好的安排就是让面向输入和输出与面向运算的应用程序合理搭配,使每个应用都能获得足够的系统服务而又互不影响。

4.合理地使用中央处理器。一般来说,在一个计算机系统中,中央处理器的速度要远远高于输入和输出的速度,因而输入和输出速度往往是影响系统性能的主要因素。但必须指出的是,这种规则只适用于普通的情况。如果不知道中央处理器能力也有一定限制,盲目地、不合理地使用中央处理器,中央处理器也会成为影响系统性能的主要因素。

通过对计算机系统的性能进行优化,排除了系统中的各种不合理因素,缩短了系统的响应时间,使计算机系统能更好地发挥作用,从而为我们提供更好的服务。

第三篇:网站前端性能优化总结

一、服务器侧优化

1.添加 Expires 或 Cache-Control 信息头

某些经常使用到、并且不会经常做改动的图片(banner、logo等等)、静态文件(登录首页、说明文档等)可以设置较长的有效期(expiration date),这些HTTP头向客户端表明了文档的有效性和持久性。如果有缓存,文档就可以从缓存(除已经过期)而不是从服务器读取。接着,客户端考察缓存中的副本,看看是否过期或者失效,以决定是否必须从服务器获得更新。

各个容器都有针对的方案,,以 Apache 为例:

ExpiresActive On ExpiresByType image/gif “access plus 1 weeks”

表示gif文件缓存一周,配置可以根据具体的业务进行调整,具体配置可以参考:http://lamp.linux.gov.cn/Apache/ApacheMenu/mod/mod_expires.html

2.压缩内容

对于绝大多数站点,这都是必要的一步,能有效减轻网络流量压力。

DeflateCompressionLevel 9 AddOutputFilterByType DEFLATE text/html text/plain text/xml application/x-httpd-php AddOutputFilter DEFLATE html htm xml php css js

表示zlib在压缩时可以最大程度的使用内存,压缩html、文本、xml和php这几种类型的文件,指定扩展名为html、htm、xml、php、css和js的文件启用压缩。

具体配置可以参考:http://lamp.linux.gov.cn/Apache/ApacheMenu/mod/mod_deflate.html

3.设置 Etags

在使用etags之前,有必要复习一下 RFC2068 中规定的返回值 200 和 304 的含义:

200--OK 304--Not Modified

客户端在请求一份文件的时候,服务端会检查客户端是否存在该文件,如果客户端不存在该文件,则下载该文件并返回200;如果客户端存在该文件并且该文件在规定期限内没有被修改(Inode,MTime和Size),则服务端只返回一个304,并不返回资源内容,客户端将会使用之前的缓存文件。而etags就是判断该文件是否被修改的记号,与服务器端的资源一一关联,所以etags对于CGI类型的页面缓存尤其有用。

下图是优化前的首页:(注意,此时没有压缩首页图片,即使使用了缓存,仍需要5s左右的时间)

化前的某页面

需要注意的是,使用etags会增加服务器端的负载,在实际应用中需要自行平衡。

二、Cookie优化

1.减小Cookie体积

HTTP coockie可以用于权限验证和个性化身份等多种用途。coockie内的有关信息是通过HTTP文件头来在web服务器和浏览器之间进行交流的。因此保持coockie尽可能的小以减少用户的响应时间十分重要。

使cookie体积尽量小;

在合适的子域名上设置bookie,以免影响其他子域名下的响应;

设置合理的过期时间,去掉不必要的cookie。

下面对比一下各个网站的cookie:

图中可以看出,6K的cookie显然是不必要的。

2.对于页面内容使用无coockie域名

当浏览器在请求中同时请求一张静态的图片和发送coockie时,服务器对于这些coockie不会做任何地使用。因此它们只是因为某些负面因素而创建的网络传输。所以你应该确定对于静态内容的请求是无coockie的请求。创建一个子域名并用他来存放所有静态内容。

例如,域名是

3.切分组件到多个域

主要的目的是提高页面组件并行下载能力,但注意,也不要同时使用过多的域名,否则就会出现第一条DNS lookup过多的问题,一般情况下两个域名就可以了。

4.杜绝 http 404 错误

对页面链接的充分测试加上对 Web 服务器 error 日志的不断跟踪可以有效减少 404 错误,并提升用户体验。

后记:

这次总结给我带来的启发并不在于提升系统性能性能本身,提升性能只是一个很表面上的东西,网上的方法有很多,测试的方法也有很多,照着都做一遍,性能确实会有所提升,但是这种知其然而不知其所以然的性能提升是没有意义的,这便是本文的目的所在。

第四篇:心得总结:Java性能优化技巧集锦

心得总结:Java性能优化技巧集锦

一、通用篇

“通用篇”讨论的问题适合于大多数Java应用。

1.1 不用new关键词创建类的实例

用new关键词创建类的实例时,构造函数链中的所有构造函数都会被自动调用。但如果一个对象实现了Cloneable接口,我们可以调用它的clone()方法。clone()方法不会调用任何类构造函数。

在使用设计模式(Design Pattern)的场合,如果用Factory模式创建对象,则改用clone()方法创建新的对象实例非常简单。例如,下面是Factory模式的一个典型实现: public static Credit getNewCredit(){ return new Credit();}

改进后的代码使用clone()方法,如下所示: private static Credit BaseCredit = new Credit();public static Credit getNewCredit(){ return(Credit)BaseCredit.clone();}

上面的思路对于数组处理同样很有用。

1.2 使用非阻塞I/O

版本较低的JDK不支持非阻塞I/O API。为避免I/O阻塞,一些应用采用了创建大量线程的办法(在较好的情况下,会使用一个缓冲池)。这种技术可以在许多必须支持并发I/O流的应用中见到,如Web服务器、报价和拍卖应用等。然而,创建Java线程需要相当可观的开销。

JDK 1.4引入了非阻塞的I/O库(java.nio)。如果应用要求使用版本较早的JDK,在这里有一个支持非阻塞I/O的软件包。

请参见Sun中国网站的《调整Java的I/O性能》。

1.3 慎用异常

异常对性能不利。抛出异常首先要创建一个新的对象。Throwable接口的构造函数调用名为fillInStackTrace()的本地(Native)方法,fillInStackTrace()方法检查堆栈,收集调用跟踪信息。只要有异常被抛出,VM就必须调整调用堆栈,因为在处理过程中创建了一个新的对象。

异常只能用于错误处理,不应该用来控制程序流程。

1.4 不要重复初始化变量

默认情况下,调用类的构造函数时,Java会把变量初始化成确定的值:所有的对象被设置成null,整数变量(byte、short、int、long)设置成0,float和 double变量设置成0.0,逻辑值设置成false。当一个类从另一个类派生时,这一点尤其应该注意,因为用new关键词创建一个对象时,构造函数链中的所有构造函数都会被自动调用。

1.5 尽量指定类的final修饰符

带有final修饰符的类是不可派生的。在Java核心API中,有许多应用final的例子,例如java.lang.String。为String类指定final防止了人们覆盖length()方法。

另外,如果指定一个类为final,则该类所有的方法都是final。Java编译器会寻找机会内联(inline)所有的final方法(这和具体的编译器实现有关)。此举能够使性能平均提高50%。

1.6 尽量使用局部变量

调用方法时传递的参数以及在调用中创建的临时变量都保存在栈(Stack)中,速度较快。其他变量,如静态变量、实例变量等,都在堆(Heap)中创建,速度较慢。另外,依赖于具体的编译器/JVM,局部变量还可能得到进一步优化。请参见《尽可能使用堆栈变量》。

1.7 乘法和除法

考虑下面的代码:

for(val = 0;val < 100000;val +=5){ alterX = val * 8;myResult = val * 2;}

用移位操作替代乘法操作可以极大地提高性能。下面是修改后的代码:

for(val = 0;val < 100000;val += 5){ alterX = val << 3;myResult = val << 1;}

修改后的代码不再做乘以8的操作,而是改用等价的左移3位操作,每左移1位相当于乘以2。相应地,右移1位操作相当于除以2。值得一提的是,虽然移位操作速度快,但可能使代码比较难于理解,所以最好加上一些注释。

二、J2EE篇

前面介绍的改善性能技巧适合于大多数Java应用,接下来要讨论的问题适合于使用JSP、EJB或JDBC的应用。

2.1 使用缓冲标记

一些应用服务器加入了面向JSP的缓冲标记功能。例如,BEA的WebLogic Server从6.0版本开始支持这个功能,Open Symphony工程也同样支持这个功能。JSP缓冲标记既能够缓冲页面片断,也能够缓冲整个页面。当JSP页面执行时,如果目标片断已经在缓冲之中,则生成该片断的代码就不用再执行。页面级缓冲捕获对指定URL的请求,并缓冲整个结果页面。对于购物篮、目录以及门户网站的主页来说,这个功能极其有用。对于这类应用,页面级缓冲能够保存页面执行的结果,供后继请求使用。

对于代码逻辑复杂的页面,利用缓冲标记提高性能的效果比较明显;反之,效果可能略逊一筹。

请参见《用缓冲技术提高JSP应用的性能和稳定性》。

2.2 始终通过会话Bean访问实体Bean

直接访问实体Bean不利于性能。当客户程序远程访问实体Bean时,每一个get方法都是一个远程调用。访问实体Bean的会话Bean是本地的,能够把所有数据组织成一个结构,然后返回它的值。

用会话Bean封装对实体Bean的访问能够改进事务管理,因为会话Bean只有在到达事务边界时才会提交。每一个对get方法的直接调用产生一个事务,容器将在每一个实体Bean的事务之后执行一个“装入-读取”操作。

一些时候,使用实体Bean会导致程序性能不佳。如果实体Bean的唯一用途就是提取和更新数据,改成在会话Bean之内利用JDBC访问数据库可以得到更好的性能。

2.3 选择合适的引用机制

在典型的JSP应用系统中,页头、页脚部分往往被抽取出来,然后根据需要引入页头、页脚。当前,在JSP页面中引入外部资源的方法主要有两种:include指令,以及include动作。

include指令:例如<%@ include file=“copyright.html” %>。该指令在编译时引入指定的资源。在编译之前,带有include指令的页面和指定的资源被合并成一个文件。被引用的外部资源在编译时就确定,比运行时才确定资源更高效。

include动作:例如。该动作引入指定页面执行后生成的结果。由于它在运行时完成,因此对输出结果的控制更加灵活。但时,只有当被引用的内容频繁地改变时,或者在对主页面的请求没有出现之前,被引用的页面无法确定时,使用include动作才合算。

2.4 在部署描述器中设置只读属性

实体Bean的部署描述器允许把所有get方法设置成“只读”。当某个事务单元的工作只包含执行读取操作的方法时,设置只读属性有利于提高性能,因为容器不必再执行存储操作。

2.5 缓冲对EJB Home的访问

EJB Home接口通过JNDI名称查找获得。这个操作需要相当可观的开销。JNDI查找最好放入Servlet的init()方法里面。如果应用中多处频繁地出现EJB访问,最好创建一个EJBHomeCache类。EJBHomeCache类一般应该作为singleton实现。

2.6 为EJB实现本地接口

本地接口是EJB 2.0规范新增的内容,它使得Bean能够避免远程调用的开销。请考虑下面的代码。PayBeanHome home =(PayBeanHome)javax.rmi.PortableRemoteObject.narrow(ctx.lookup(“PayBeanHome”), PayBeanHome.class);PayBean bean =(PayBean)javax.rmi.PortableRemoteObject.narrow(home.create(), PayBean.class);

第一个语句表示我们要寻找Bean的Home接口。这个查找通过JNDI进行,它是一个RMI调用。然后,我们定位远程对象,返回代理引用,这也是一个 RMI调用。第二个语句示范了如何创建一个实例,涉及了创建IIOP请求并在网络上传输请求的stub程序,它也是一个RMI调用。

要实现本地接口,我们必须作如下修改:

方法不能再抛出java.rmi.RemoteException异常,包括从RemoteException派生的异常,比如 TransactionRequiredException、TransactionRolledBackException和 NoSuchObjectException。EJB提供了等价的本地异常,如TransactionRequiredLocalException、TransactionRolledBackLocalException和NoSuchObjectLocalException。

所有数据和返回值都通过引用的方式传递,而不是传递值。

本地接口必须在EJB部署的机器上使用。简而言之,客户程序和提供服务的组件必须在同一个JVM上运行。

如果Bean实现了本地接口,则其引用不可串行化。

请参见《用本地引用提高EJB访问效率》。

软件资讯 > 开发特区 > 开发语言 > Java

2006年05月11日 作者:songxin 责任编辑:xietaoming

文章导读:分通用篇、J2EE篇、GUI篇三部分介绍Java性能优化技巧。

2.7 生成主键

在EJB之内生成主键有许多途径,下面分析了几种常见的办法以及它们的特点。

利用数据库内建的标识机制(SQL Server的IDENTITY或Oracle的SEQUENCE)。这种方法的缺点是EJB可移植性差。

由实体Bean自己计算主键值(比如做增量操作)。它的缺点是要求事务可串行化,而且速度也较慢。

利用NTP之类的时钟服务。这要求有面向特定平台的本地代码,从而把Bean固定到了特定的OS之上。另外,它还导致了这样一种可能,即在多CPU的服务器上,同一个毫秒之内生成了两个主键。

借鉴Microsoft的思路,在Bean中创建一个GUID。然而,如果不求助于JNI,Java不能确定网卡的MAC地址;如果使用JNI,则程序就要依赖于特定的OS。

还有其他几种办法,但这些办法同样都有各自的局限。似乎只有一个答案比较理想:结合运用RMI和JNDI。先通过RMI注册把RMI远程对象绑定到JNDI树。客户程序通过JNDI进行查找。下面是一个例子: public class keyGenerator extends UnicastRemoteObject implements Remote { private static long KeyValue = System.currentTimeMillis();public static synchronized long getKey()throws RemoteException { return KeyValue++;}

2.8 及时清除不再需要的会话

为了清除不再活动的会话,许多应用服务器都有默认的会话超时时间,一般为30分钟。当应用服务器需要保存更多会话时,如果内存容量不足,操作系统会把部分内存数据转移到磁盘,应用服务器也可能根据“最近最频繁使用”(Most Recently Used)算法把部分不活跃的会话转储到磁盘,甚至可能抛出“内存不足”异常。在大规模系统中,串行化会话的代价是很昂贵的。当会话不再需要时,应当及时调用HttpSession.invalidate()方法清除会话。HttpSession.invalidate()方法通常可以在应用的退出页面调用。

2.9 在JSP页面中关闭无用的会话

对于那些无需跟踪会话状态的页面,关闭自动创建的会话可以节省一些资源。使用如下page指令: <%@ page session=“false”%>

2.10 Servlet与内存使用

许多开发者随意地把大量信息保存到用户会话之中。一些时候,保存在会话中的对象没有及时地被垃圾回收机制回收。从性能上看,典型的症状是用户感到系统周期性地变慢,却又不能把原因归于任何一个具体的组件。如果监视JVM的堆空间,它的表现是内存占用不正常地大起大落。

解决这类内存问题主要有二种办法。第一种办法是,在所有作用范围为会话的Bean中实现HttpSessionBindingListener接口。这样,只要实现valueUnbound()方法,就可以显式地释放Bean使用的资源。

另外一种办法就是尽快地把会话作废。大多数应用服务器都有设置会话作废间隔时间的选项。另外,也可以用编程的方式调用会话的 setMaxInactiveInterval()方法,该方法用来设定在作废会话之前,Servlet容器允许的客户请求的最大间隔时间,以秒计。

2.11 HTTP Keep-Alive

Keep-Alive功能使客户端到服务器端的连接持续有效,当出现对服务器的后继请求时,Keep-Alive功能避免了建立或者重新建立连接。市场上的大部分Web服务器,包括iPlanet、IIS和Apache,都支持HTTP Keep-Alive。对于提供静态内容的网站来说,这个功能通常很有用。但是,对于负担较重的网站来说,这

里存在另外一个问题:虽然为客户保留打开的连接有一定的好处,但它同样影响了性能,因为在处理暂停期间,本来可以释放的资源仍旧被占用。当Web服务器和应用服务器在同一台机器上运行时,Keep-Alive功能对资源利用的影响尤其突出。

2.12 JDBC与Unicode

想必你已经了解一些使用JDBC时提高性能的措施,比如利用连接池、正确地选择存储过程和直接执行的SQL、从结果集删除多余的列、预先编译SQL语句,等等。

除了这些显而易见的选择之外,另一个提高性能的好选择可能就是把所有的字符数据都保存为Unicode(代码页13488)。Java以Unicode形式处理所有数据,因此,数据库驱动程序不必再执行转换过程。但应该记住:如果采用这种方式,数据库会变得更大,因为每个Unicode字符需要2个字节存储空间。另外,如果有其他非Unicode的程序访问数据库,性能问题仍旧会出现,因为这时数据库驱动程序仍旧必须执行转换过程。

2.13 JDBC与I/O

如果应用程序需要访问一个规模很大的数据集,则应当考虑使用块提取方式。默认情况下,JDBC每次提取32行数据。举例来说,假设我们要遍历一个5000 行的记录集,JDBC必须调用数据库157次才能提取到全部数据。如果把块大小改成512,则调用数据库的次数将减少到10次。

在一些情形下这种技术无效。例如,如果使用可滚动的记录集,或者在查询中指定了FOR UPDATE,则块操作方式不再有效。

2.14 内存数据库

许多应用需要以用户为单位在会话对象中保存相当数量的数据,典型的应用如购物篮和目录等。由于这类数据可以按照行/列的形式组织,因此,许多应用创建了庞大的Vector或HashMap。在会话中保存这类数据极大地限制了应用的可伸缩性,因为服务器拥有的内存至少必须达到每个会话占用的内存数量乘以并发用户最大数量,它不仅使服务器价格昂贵,而且垃圾收集的时间间隔也可能延长到难以忍受的程度。

一些人把购物篮/目录功能转移到数据库层,在一定程度上提高了可伸缩性。然而,把这部分功能放到数据库层也存在问题,且问题的根源与大多数关系数据库系统的体系结构有关。对于关系数据库来说,运行时的重要原则之一是确保所有的写入操作稳定、可靠,因而,所有的性能问题都与物理上把数据写入磁盘的能力有关。关系数据库力图减少I/O操作,特别是对于读操作,但实现该目标的主要途径只是执行一套实现缓冲机制的复杂算法,而这正是数据库层第一号性能瓶颈通常总是 CPU的主要原因。

一种替代传统关系数据库的方案是,使用在内存中运行的数据库(In-memory Database),例如TimesTen。内存数据库的出发点是允许数据临时地写入,但这些数据不必永久地保存到磁盘上,所有的操作都在内存中进行。这样,内存数据库不需要复杂的算法来减少I/O操作,而且可以采用比较简单的加锁机制,因而速度很快。

三、GUI篇

这一部分介绍的内容适合于图形用户界面的应用(Applet和普通应用),要用到AWT或Swing。

3.1 用JAR压缩类文件

Java档案文件(JAR文件)是根据JavaBean标准压缩的文件,是发布JavaBean组件的主要方式和推荐方式。JAR档案有助于减少文件体积,缩短下载时间。例如,它有助于Applet提高启动速度。一个JAR文件可以包含一个或者多个相关的Bean以及支持文件,比如图形、声音、HTML 和其他资源。

要在HTML/JSP文件中指定JAR文件,只需在Applet标记中加入ARCHIVE = “name.jar”声明。

请参见《使用档案文件提高 applet 的加载速度》。

3.2 提示Applet装入进程

你是否看到过使用Applet的网站,注意到在应该运行Applet的地方出现了一个占位符?当Applet的下载时间较长时,会发生什么事情?最大的可能就是用户掉头离去。在这种情况下,显示一个Applet正在下载的信息无疑有助于鼓励用户继续等待。

下面我们来看看一种具体的实现方法。首先创建一个很小的Applet,该Applet负责在后台下载正式的Applet:

import java.applet.Applet;import java.applet.AppletStub;import java.awt.Label;import java.awt.Graphics;import java.awt.GridLayout;public class PreLoader extends Applet implements Runnable, AppletStub { String largeAppletName;Label label;public void init(){ // 要求装载的正式Applet largeAppletName = getParameter(“applet”);// “请稍等”提示信息

label = new Label(“请稍等...” + largeAppletName);add(label);} public void run(){ try { // 获得待装载Applet的类

Class largeAppletClass = Class.forName(largeAppletName);// 创建待装载Applet的实例

Applet largeApplet =(Applet)largeAppletClass.newInstance();// 设置该Applet的Stub程序 largeApplet.setStub(this);// 取消“请稍等”信息 remove(label);// 设置布局

setLayout(new GridLayout(1, 0));add(largeApplet);// 显示正式的Applet largeApplet.init();largeApplet.start();} catch(Exception ex){ // 显示错误信息

label.setText(“不能装入指定的Applet”);} // 刷新屏幕 validate();

} public void appletResize(int width, int height){ // 把appletResize调用从stub程序传递到Applet resize(width, height);} }

编译后的代码小于2K,下载速度很快。代码中有几个地方值得注意。首先,PreLoader实现了AppletStub接口。一般地,Applet从调用者判断自己的codebase。在本例中,我们必须调用setStub()告诉Applet到哪里提取这个信息。另一个值得注意的地方是,AppletStub接口包含许多和Applet类一样的方法,但appletResize()方法除外。这里我们把对appletResize()方法的调用传递给了resize()方法。

3.3 在画出图形之前预先装入它

ImageObserver接口可用来接收图形装入的提示信息。ImageObserver接口只有一个方法imageUpdate(),能够用一次repaint()操作在屏幕上画出图形。下面提供了一个例子。public boolean imageUpdate(Image img, int flags, int x, int y, int w, int h){ if((flags & ALLBITS)!=0 { repaint();} else if(flags &(ERROR |ABORT))!= 0){ error = true;// 文件没有找到,考虑显示一个占位符 repaint();} return(flags &(ALLBITS | ERROR| ABORT))== 0;}

当图形信息可用时,imageUpdate()方法被调用。如果需要进一步更新,该方法返回true;如果所需信息已经得到,该方法返回false。

3.4 覆盖update方法

update()方法的默认动作是清除屏幕,然后调用paint()方法。如果使用默认的update()方法,频繁使用图形的应用可能出现显示闪烁现象。要避免在paint()调用之前的屏幕清除操作,只需按照如下方式覆盖update()方法:

public void update(Graphics g){ paint(g);}

更理想的方案是:覆盖update(),只重画屏幕上发生变化的区域,如下所示: public void update(Graphics g){ g.clipRect(x, y, w, h);paint(g);}

3.5 延迟重画操作

对于图形用户界面的应用来说,性能低下的主要原因往往可以归结为重画屏幕的效率低下。当用户改变窗口大小或者滚动一个窗口时,这一点通常可以很明显地观察到。改变窗口大小或者滚动屏幕之类的操作导致重画屏幕事件大量地、快速地生成,甚至超过了相关代码的执行速度。对付这个问题最好的办法是忽略所有“迟到” 的事件。

建议在这里引入一个数毫秒的时差,即如果我们立即接收到了另一个重画事件,可以停止处理当前事件转而处理最后一个收到的重画事件;否则,我们继续进行当前的重画过程。

如果事件要启动一项耗时的工作,分离出一个工作线程是一种较好的处理方式;否则,一些部件可能被“冻结”,因为每次只能处理一个事件。下面提供了一个事件处理的简单例子,但经过扩展后它可以用来控制工作线程。

public static void runOnce(String id, final long milliseconds){ synchronized(e_queue){ // e_queue: 所有事件的集合 if(!e_queue.containsKey(id)){ e_queue.put(token, new LastOne());} } final LastOne lastOne =(LastOne)e_queue.get(token);final long time = System.currentTimeMillis();// 获得当前时间 lastOne.time = time;(new Thread(){public void run(){ if(milliseconds > 0){ try {Thread.sleep(milliseconds);} // 暂停线程 catch(Exception ex){} } synchronized(lastOne.running){ // 等待上一事件结束 if(lastOne.time!= time)// 只处理最后一个事件 return;} }}).start();} private static Hashtable e_queue = new Hashtable();private static class LastOne { public long time=0;public Object running = new Object();}

3.6 使用双缓冲区

在屏幕之外的缓冲区绘图,完成后立即把整个图形显示出来。由于有两个缓冲区,所以程序可以来回切换。这样,我们可以用一个低优先级的线程负责画图,使得程序能够利用空闲的CPU时间执行其他任务。下面的伪代码片断示范了这种技术。Graphics myGraphics;Image myOffscreenImage = createImage(size().width, size().height);

Graphics offscreenGraphics = myOffscreenImage.getGraphics();offscreenGraphics.drawImage(img, 50, 50, this);myGraphics.drawImage(myOffscreenImage, 0, 0, this);

3.7 使用BufferedImage

Java JDK 1.2使用了一个软显示设备,使得文本在不同的平台上看起来相似。为实现这个功能,Java必须直接处理构成文字的像素。由于这种技术要在内存中大量地进行位复制操作,早期的JDK在使用这种技术时性能不佳。为解决这个问题而提出的Java标准实现了一种新的图形类型,即BufferedImage。

BufferedImage子类描述的图形带有一个可访问的图形数据缓冲区。一个BufferedImage包含一个ColorModel和一组光栅图形数据。这个类一般使用RGB(红、绿、蓝)颜色模型,但也可以处理灰度级图形。它的构造函数很简单,如下所示:

public BufferedImage(int width, int height, int imageType)

ImageType允许我们指定要缓冲的是什么类型的图形,比如5-位RGB、8-位RGB、灰度级等。

3.8 使用VolatileImage

许多硬件平台和它们的操作系统都提供基本的硬件加速支持。例如,硬件加速一般提供矩形填充功能,和利用CPU完成同一任务相比,硬件加速的效率更高。由于硬件加速分离了一部分工作,允许多个工作流并发进行,从而缓解了对CPU和系统总线的压力,使得应用能够运行得更快。利用VolatileImage可以创建硬件加速的图形以及管理图形的内容。由于它直接利用低层平台的能力,性能的改善程度主要取决于系统使用的图形适配器。VolatileImage的内容随时可能丢失,也即它是“不稳定的(volatile)”。因此,在使用图形之前,最好检查一下它的内容是否丢失。VolatileImage有两个能够检查内容是否丢失的方法: public abstract int validate(GraphicsConfiguration gc);public abstract Boolean contentsLost();

每次从VolatileImage对象复制内容或者写入VolatileImage时,应该调用validate()方法。contentsLost()方法告诉我们,自从最后一次validate()调用之后,图形的内容是否丢失。

虽然VolatileImage是一个抽象类,但不要从它这里派生子类。VolatileImage应该通过

Component.createVolatileImage()或者 GraphicsConfiguration.createCompatibleVolatileImage()方法创建。

3.9 使用Window Blitting

进行滚动操作时,所有可见的内容一般都要重画,从而导致大量不必要的重画工作。许多操作系统的图形子系统,包括WIN32 GDI、MacOS和X/Windows,都支持Window Blitting技术。Window Blitting技术直接在屏幕缓冲区中把图形移到新的位置,只重画新出现的区域。要在Swing应用中使用Window Blitting技术,设置方法如下: setScrollMode(int mode);

在大多数应用中,使用这种技术能够提高滚动速度。只有在一种情形下,Window Blitting会导致性能降低,即应用在后台进行滚动操作。如果是用户在滚动一个应用,那么它总是在前台,无需担心任何负面影响。

第五篇:。NET 性能优化方法总结与字符串连接优化

.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该类型的所有实例,这不仅可能导致严重的性能问题,还可能导致一些无法预期的行为。这是一个很不好的习惯。即便对于一个只包含static方法的类型,也应额外构造一个static的成员变量,让此成员变量作为锁定对象。

避免锁定 this

锁定 this 会影响该实例的所有方法。假设对象 obj 有 A 和 B 两个方法,其中 A 方法使用 lock(this)对方法中的某段代码设置同步保护。现在,因为某种原因,B 方法也开始使用 lock(this)来设置同步保护了,并且可能为了完全不同的目的。这样,A 方法就被干扰了,其行为可能无法预知。所以,作为一种良好的习惯,建议避免使用 lock(this)这种方式。

使用为同步目的专门构造的成员变量

这是推荐的做法。方式就是 new 一个 object 对象,该对象仅仅用于同步目的。

如果有多个方法都需要同步,并且有不同的目的,那么就可以为些分别建立几个同步成员变量。

1.3.1.4 集合同步

C#为各种集合类型提供了两种方便的同步机制:Synchronized 包装器和 SyncRoot 属性。

// Creates and initializes a new ArrayList ArrayList myAL = new ArrayList();myAL.Add(” The “);myAL.Add(” quick “);myAL.Add(” brown “);myAL.Add(” fox “);

// Creates a synchronized wrapper around the ArrayList ArrayList mySyncdAL = ArrayList.Synchronized(myAL);

调用 Synchronized 方法会返回一个可保证所有操作都是线程安全的相同集合对象。考虑 mySyncdAL[0] = mySyncdAL[0] + ”test“ 这一语句,读和写一共要用到两个锁。一般讲,效率不高。推荐使用 SyncRoot 属性,可以做比较精细的控制。

1.3.2 使用 ThreadStatic 替代 NameDataSlot ★ 存取 NameDataSlot 的 Thread.GetData 和 Thread.SetData 方法需要线程同步,涉及两个锁:一个是 LocalDataStore.SetData 方法需要在 AppDomain 一级加锁,另一个是 ThreadNative.GetDomainLocalStore 方法需要在 Process 一级加锁。如果一些底层的基础服务使用了 NameDataSlot,将导致系统出现严重的伸缩性问题。

规避这个问题的方法是使用 ThreadStatic 变量。示例如下:

public sealed class InvokeContext {

[ThreadStatic]

private static InvokeContext current;

private Hashtable maps = new Hashtable();}

1.3.3 多线程编程技巧

1.3.3.1 使用 Double Check 技术创建对象

internal IDictionary KeyTable { get {

if(this._keyTable == null){

lock(base._lock){

if(this._keyTable == null){

this._keyTable = new Hashtable();} } }

return this._keyTable;} }

创建单例对象是很常见的一种编程情况。一般在 lock 语句后就会直接创建对象了,但这不够安全。因为在 lock 锁定对象之前,可能已经有多个线程进入到了第一个 if 语句中。如果不加第二个 if 语句,则单例对象会被重复创建,新的实例替代掉旧的实例。如果单例对象中已有数据不允许被破坏或者别的什么原因,则应考虑使用 Double Check 技术。

1.4 类型系统 1.4.1 避免无意义的变量初始化动作

CLR保证所有对象在访问前已初始化,其做法是将分配的内存清零。因此,不需要将变量重新初始化为0、false或null。

需要注意的是:方法中的局部变量不是从堆而是从栈上分配,所以C#不会做清零工作。如果使用了未赋值的局部变量,编译期间即会报警。不要因为有这个印象而对所有类的成员变量也做赋值动作,两者的机理完全不同!1.4.2 ValueType 和 ReferenceType

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

值类型从调用栈分配,引用类型从托管堆分配。当值类型用作方法参数时,默认会进行参数值复制,这抵消了值类型分配效率上的优势。作为一项基本技巧,以引用方式传递值类型参数可以提高性能。

1.4.2.2 为 ValueType 提供 Equals 方法

.net 默认实现的 ValueType.Equals 方法使用了反射技术,依靠反射来获得所有成员变量值做比较,这个效率极低。如果我们编写的值对象其 Equals 方法要被用到(例如将值对象放到 HashTable 中),那么就应该重载 Equals 方法。

public struct Rectangle {

public double Length;public double Breadth;

public override bool Equals(object ob){

if(ob is Rectangle)

return Equels((Rectangle)ob))else

return false;}

private bool Equals(Rectangle rect){

return this.Length == rect.Length && this.Breadth == rect.Breach;} }

1.4.2.3 避免装箱和拆箱

C#可以在值类型和引用类型之间自动转换,方法是装箱和拆箱。装箱需要从堆上分配对象并拷贝值,有一定性能消耗。如果这一过程发生在循环中或是作为底层方法被频繁调用,则应该警惕累计的效应。

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

ArrayList al = new ArrayList();for(int i = 0;i < 1000;i ++){ al.Add(i);// Implicitly boxed because Add()takes an object }

int f =(int)al[ 0 ];// The element is unboxed

但是得当心!如果你像使用引用类型那么频繁的使用一个值类型的话,值类型的优势会很快被耗尽。比如,把一个值类型压到一个含有对象类型的群集。这叫做装箱,很耗用处理器周期,尤其是当你的代码在把它作为值(对它进行数学运算)和把它作为引用之间来回运行时。

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

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

• 使用泛型来创建群集和其它的数据结构,这样,在运行时,它们就可以被实例化来存储刚好合适的类型。这节省了装箱/拆箱和类型转换的时间。

• 在C#中使用as,而不是is。关键字is用来查看引用是否可以被作为某个具体的类型,但是并不返回转换到这个类型的引用。所以,通常当你从is获得一个正的结果时,你首先应该cast——有效地执行两次cast。采用as关键词时,如果可用,则返回cast为新类型的引用;否则返回null。你可以查看null然后做你喜欢做的事情。整体来说,As方法要比is方法快50%。

1.5 异常处理

异常也是现代语言的典型特征。与传统检查错误码的方式相比,异常是强制性的(不依赖于是否忘记了编写检查错误码的代码)、强类型的、并带有丰富的异常信息(例如调用栈)。1.5.1 不要吃掉异常★

关于异常处理的最重要原则就是:不要吃掉异常。这个问题与性能无关,但对于编写健壮和易于排错的程序非常重要。这个原则换一种说法,就是不要捕获那些你不能处理的异常。

吃掉异常是极不好的习惯,因为你消除了解决问题的线索。一旦出现错误,定位问题将非常困难。除了这种完全吃掉异常的方式外,只将异常信息写入日志文件但并不做更多处理的做法也同样不妥。1.5.2 不要吃掉异常信息★

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

为异常披露详尽的信息是程序员的职责所在。如果不能在保留原始异常信息含义的前提下附加更丰富和更人性化的内容,那么让原始的异常信息直接展示也要强得多。千万不要吃掉异常。1.5.3 避免不必要的抛出异常 抛出异常和捕获异常属于消耗比较大的操作,在可能的情况下,应通过完善程序逻辑避免抛出不必要不必要的异常。与此相关的一个倾向是利用异常来控制处理逻辑。尽管对于极少数的情况,这可能获得更为优雅的解决方案,但通常而言应该避免。1.5.4 避免不必要的重新抛出异常

如果是为了包装异常的目的(即加入更多信息后包装成新异常),那么是合理的。但是有不少代码,捕获异常没有做任何处理就再次抛出,这将无谓地增加一次捕获异常和抛出异常的消耗,对性能有伤害。

1.5.5 捕获指定的异常,不要使用通用的System.Exception.//避免

try

{

}

catch(Exception exc)

{

}

//推荐

try

{

}

catch(System.NullReferenceException exc)

{

}

catch(System.ArgumentOutOfRangeException exc)

{

}

catch(System.InvalidCastException exc)

{

}

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

使用Try...catch...finally时,要在finally里释放占用的资源如连接,文件流等,不然在Catch到错误后占用的资源不能释放。

try

{

...}

catch

{...}

finally

{

conntion.close()

}

1.6 反射

反射是一项很基础的技术,它将编译期间的静态绑定转换为延迟到运行期间的动态绑定。在很多场景下(特别是类框架的设计),可以获得灵活易于扩展的架构。但带来的问题是与静态绑定相比,动态绑定会对性能造成较大的伤害。1.6.1 反射分类

type comparison :类型判断,主要包括 is 和 typeof 两个操作符及对象实例上的 GetType 调用。这是最轻型的消耗,可以无需考虑优化问题。注意 typeof 运算符比对象实例上的 GetType 方法要快,只要可能则优先使用 typeof 运算符。

member enumeration : 成员枚举,用于访问反射相关的元数据信息,例如Assembly.GetModule、Module.GetType、Type对象上的IsInterface、IsPublic、GetMethod、GetMethods、GetProperty、GetProperties、GetConstructor调用等。尽管元数据都会被CLR缓存,但部分方法的调用消耗仍非常大,不过这类方法调用频度不会很高,所以总体看性能损失程度中等。

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

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

1.Type.InvokeMember

2.ContructorInfo.Invoke

3.Activator.CreateInstance(Type)

4.Activator.CreateInstance(assemblyName, typeName)

5.Assembly.CreateInstance(typeName)

最快的是方式 3,与 Direct Create 的差异在一个数量级之内,约慢 7 倍的水平。其他方式,至少在 40 倍以上,最慢的是方式 4,要慢三个数量级。1.6.3 动态方法调用

方法调用分为编译期的早期绑定和运行期的动态绑定两种,称为Early-Bound Invocation和Late-Bound Invocation。Early-Bound Invocation可细分为Direct-call、Interface-call和Delegate-call。Late-Bound Invocation主要有Type.InvokeMember和MethodBase.Invoke,还可以通过使用LCG(Lightweight Code Generation)技术生成IL代码来实现动态调用。

从测试结果看,相比Direct Call,Type.InvokeMember要接近慢三个数量级;MethodBase.Invoke虽然比Type.InvokeMember要快三倍,但比Direct Call仍慢270倍左右。可见动态方法调用的性能是非常低下的。我们的建议是:除非要满足特定的需求,否则不要使用!1.6.4 推荐的使用原则

模式

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

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

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

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

反模式

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

1.7 基本代码技巧

这里描述一些应用场景下,可以提高性能的基本代码技巧。对处于关键路径的代码,进行这类的优化还是很有意义的。普通代码可以不做要求,但养成一种好的习惯也是有意义的。1.7.1 循环写法

可以把循环的判断条件用局部变量记录下来。局部变量往往被编译器优化为直接使用寄存器,相对于普通从堆或栈中分配的变量速度快。如果访问的是复杂计算属性的话,提升效果将更明显。for(int i = 0, j = collection.GetIndexOf(item);i < j;i++)

需要说明的是:这种写法对于CLR集合类的Count属性没有意义,原因是编译器已经按这种方式做了特别的优化。1.7.2 拼装字符串

拼装好之后再删除是很低效的写法。有些方法其循环长度在大部分情况下为1,这种写法的低效就更为明显了:

public static string ToString(MetadataKey entityKey){

string str = ”“;

object [] vals = entityKey.values;for(int i = 0;i < vals.Length;i ++){

str += ” , “ + vals[i].ToString();}

return str == ”“ ? ”“ : str.Remove(0 , 1);}

推荐下面的写法:

if(str.Length == 0)str = vals[i].ToString();else

str += ” , “ + vals[i].ToString();其实这种写法非常自然,而且效率很高,完全不需要用个Remove方法绕来绕去。1.7.3 避免两次检索集合元素

获取集合元素时,有时需要检查元素是否存在。通常的做法是先调用ContainsKey(或Contains)方法,然后再获取集合元素。这种写法非常符合逻辑。

但如果考虑效率,可以先直接获取对象,然后判断对象是否为null来确定元素是否存在。对于Hashtable,这可以节省一次GetHashCode调用和n次Equals比较。

如下面的示例:

public IData GetItemByID(Guid id){

IData data1 = null;

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

data1 = this.idTable[id.ToString()] as IData;}

return data1;}

其实完全可用一行代码完成:return this.idTable[id] as IData;1.7.4 避免两次类型转换

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

if(obj is SomeType){

SomeType st =(SomeType)obj;st.SomeTypeMethod();}

效率更高的做法如下:

SomeType st = obj as SomeType;if(st!= null){

st.SomeTypeMethod();}

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

//避免

//

MyObject obj = new MyObject();

obj.Status = ”ACTIVE“;

//推荐

const string C_STATUS = ”ACTIVE“;

MyObject obj = new MyObject();

obj.Status = C_STATUS;

1.7.6 用StringBuilder代替使用字符串连接符

//避免

String sXML = ” “;

sXML += ”“;

sXML += ”Data“;

sXML += ”“;

sXML += ”“;

//推荐

StringBuilder sbXML = new StringBuilder();

sbXML.Append(” “);

sbXML.Append(”“);

sbXML.Append(”Data“);

sbXML.Append(”“);

sbXML.Append(”“);

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

//避免

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

+”

{

SomeClass objSC = new SomeClass();} //推荐

SomeClass objSC = null;for(int i=0;i<10;i++){

objSC = new SomeClass();)

1.8 Hashtable 1.8.1 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每次都不同,所以这个CompiledQueryCache根本就没有起到预想的作用。这个小小的疏漏带来了重大的性能问题,由于解析OQL表达式频繁发生,导致CompiledQueryCache不断增长,造成服务器内存泄漏!解决这个问题的最简单方法就是提供一个常量实现,例如让散列码为常量0。虽然这会导致所有对象汇聚到同一个存储桶中,效率不高,但至少可以解决掉内存泄漏问题。当然,最终还是会实现一个高效的GetHashCode方法的。

以上介绍这些Hashtable机理,主要是希望大家理解:如果使用Hashtable,你应该检查一下对象是否提供了适当的GetHashCode和Equals方法实现。否则,有可能出现效率不高或者与预期行为不符的情况。

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

其他字典集合类型(如StringDictionary,NameValueCollection,HybridCollection),存放少量数据的时候可以使用HashTable。很多非泛型集合类都有对应的泛型集合类,下面是常用的非泛型集合类以及对应的泛型集合类:

非泛型集合类 泛型集合类

ArrayList List

HashTable DIctionary Queue Queue Stack Stack

SortedList SortedList

我们用的比较多的非泛型集合类主要有 ArrayList类 和 HashTable类。我们经常用HashTable 来存储将要写入到数据库或者返回的信息,在这之间要不断的进行类型的转化,增加了系统装箱和拆箱的负担,如果我们操纵的数据类型相对确定的化

用 Dictionary 集合类来存储数据就方便多了,例如我们需要在电子商务网站中存储用户的购物车信息(商品名,对应的商品个数)时,完全可以用 Dictionary 来存储购物车信息,而不需要任何的类型转化。

1.9 避免使用ArrayList。

因为任何对象添加到ArrayList都要封箱为System.Object类型,从ArrayList取出数据时,要拆箱回实际的类型。建议使用自定义的集合类型代替ArrayList。.net 2.0提供了一个新的类型,叫泛型,这是一个强类型,使用泛型集合就可以避免了封箱和拆箱的发生,提高了性能。

1.10从XML对象读取数据

如果只是从XML对象读取数据,用只读的XPathDocument代替XMLDocument,可以提高性能

//避免

XmlDocument xmld = new XmlDocument();

xmld.LoadXml(sXML);

txtName.Text = xmld.SelectSingleNode(”/packet/child“).InnerText;

.//推荐

XPathDocument xmldContext = new XPathDocument(new StringReader(oContext.Value));

XPathNavigator xnav = xmldContext.CreateNavigator();

XPathNodeIterator xpNodeIter = xnav.Select(”packet/child“);

iCount = xpNodeIter.Count;

xpNodeIter = xnav.SelectDescendants(XPathNodeType.Element, false);

while(xpNodeIter.MoveNext())

{

sCurrValues += xpNodeIter.Current.Value+”~“;

}

}

1.11 避免使用递归调用和嵌套循环,使用他们会严重影响性能,在不得不用的时候才使用。

1.12 使用适当的Caching策略来提高性能

2.Ado.Net

2.1 应用Ado.net的一些思考原则

1.根据数据使用的方式来设计数据访问层 2.缓存数据,避免不必要的操作 3.使用服务帐户进行连接 4.必要时申请,尽早释放 5.关闭可关闭的资源 6.减少往返

7.仅返回需要的数据 8.选择适当的事务类型 9.使用存储过程

2.2 Connection 数据库连接是一种共享资源,并且打开和关闭的开销较大。Ado.net默认启用了连接池机制,关闭连接不会真的关闭物理连接,而只是把连接放回到连接池中。因为池中共享的连接资源始终是有限的,如果在使用连接后不尽快关闭连接,那么就有可能导致申请连接的线程被阻塞住,影响整个系统的性能表现。2.2.1 在方法中打开和关闭连接

这个原则有几层含义:

1.主要目的是为了做到必要时申请和尽早释放

2.不要在类的构造函数中打开连接、在析构函数中释放连接。因为这将依赖于垃圾回收,而垃圾回收只受内存影响,回收时机不定

3.不要在方法之间传递连接,这往往导致连接保持打开的时间过长

这里强调一下在方法之间传递连接的危害:曾经在压力测试中遇到过一个测试案例,当增大用户数的时候,这个案例要比别的案例早很久就用掉连接池中的所有连接。经分析,就是因为A方法把一个打开的连接传递到了B方法,而B方法又调用了一个自行打开和关闭连接的C方法。在A方法的整个运行期间,它至少需要占用两条连接才能够成功工作,并且其中的一条连接占用时间还特别长,所以造成连接池资源紧张,影响了整个系统的可伸缩性!

2.2.2 显式关闭连接

Connection对象本身在垃圾回收时可以被关闭,而依赖垃圾回收是很不好的策略。推荐使用using语句显式关闭连接,如下例:

using(SqlConnection conn = new SqlConnection(connString)){

conn.Open();

} // Dispose is automatically called on the conn variable here

2.2.3 确保连接池启用

Ado.net是为每个不同的连接串建立连接池,因此应该确保连接串不会出现与具体用户相关的信息。另外,要注意连接串是大小写敏感的。2.2.4 不要缓存连接

例如,把连接缓存到Session或Application中。在启用连接池的情况下,这种做法没有任何意义。

2.3 Command

2.3.1 使用ExecuteScalar和ExecuteNonQuery

如果想返回像Count(*)、Sum(Price)或Avg(Quantity)那样的单值,可以使用ExecuteScalar方法。ExecuteScalar返回第一行第一列的值,将结果集作为标量值返回。因为单独一步就能完成,所以ExecuteScalar不仅简化了代码,还提高了性能。

使用不返回行的SQL语句时,例如修改数据(INSERT、UPDATE或DELETE)或仅返回输出参数或返回值,请使用ExecuteNonQuery。这避免了用于创建空DataReader的任何不必要处理。2.3.2 使用Prepare

当需要重复执行同一SQL语句多次,可考虑使用Prepare方法提升效率。需要注意的是,如果只是执行一次或两次,则完全没有必要。例如:

cmd.CommandText = ”insert into Table1(Col1, Col2)values(@val1, @val2)“;

cmd.Parameters.Add(”@val1“, SqlDbType.Int, 4, ”Col1“);cms.Parameters.Add(”@val2“, SqlDbType.NChar, 50, ”Col2“);

cmd.Parameters[0].Value = 1;

cmd.Parameters[1].Value = ”XXX“;cmd.Prepare();

cmd.ExecuteNonQuery();

cmd.Parameters[0].Value = 2;

cmd.Parameters[1].Value = ”YYY“;cmd.ExecuteNonQuery();

cmd.Parameters[0].Value = 3;

cmd.Parameters[1].Value = ”ZZZ“;cmd.ExecuteNonQuery();

2.3.3 使用绑定变量 ★

SQL语句需要先被编译成执行计划,然后再执行。如果使用绑定变量的方式,那么这个执行计划就可以被后续执行的SQL语句所复用。而如果直接把参数合并到了SQL语句中,由于参数值千变万化,执行计划就难以被复用了。例如上面Prepare一节给出的示例,如果把参数值直接写到insert语句中,那么上面的四次调用将需要编译四次执行计划。

为避免这种情况造成性能损失,要求一律使用绑定变量方式。

2.4 DataReader

DataReader最适合于访问只读的单向数据集。与DataSet不同,数据集并不全部在内存中,而是随不断发出的read请求,一旦发现数据缓冲区中的数据均被读取,则从数据源传输一个数据缓冲区大小的数据块过来。另外,DataReader保持连接,DataSet则与连接断开。2.4.1 显式关闭DataReader

与连接类似,也需要显式关闭DataReader。另外,如果与DataReader关联的Connection仅为DataReader服务的话,可考虑使用Command对象的ExecuteReader(CommandBehavior.CloseConnection)方式。这可以保证当DataReader关闭时,同时自动关闭Connection。2.4.2 用索引号访问代替名称索引号访问属性

从Row中访问某列属性,使用索引号的方式比使用名称方式有细微提高。如果会被频繁调用,例如在循环中,那么可考虑此类优化。示例如下:

cmd.CommandText = ”select Col1, Col2 from Table1“;SqlDataReader dr = cmd.ExecuteReader();

int col1 = dr.GetOrdinal(”Col1“);int col2 = dr.GetOrdinal(”Col2“);

while(dr.Read()){

Console.WriteLine(dr[col1] + ”_“ + dr[col2]);}

2.4.3 使用类型化方法访问属性

从Row中访问某列属性,用GetString、GetInt32这种显式指明类型的方法,其效率较通用的GetValue方法有细微提高,因为不需要做类型转换。2.4.4 使用多数据集

部分场景可以考虑一次返回多数据集来降低网络交互次数,提升效率。示例如下:

cmd.CommandText = ”StoredProcedureName“;// The stored procedure returns multiple result sets.SqlDataReader dr = cmd.ExecuteReader();

while(dr.read())// read first result set

dr.NextResult();

while(dr.read())//

2.5 DataSet

2.5.1 利用索引加快查找行的效率

如果需要反复查找行,建议增加索引。有两种方式:

1.设置DataTable的PrimaryKey

适用于按PrimaryKey查找行的情况。注意此时应调用DataTable.Rows.Find方法,一般惯用的Select方法不能利用索引。2.使用DataView

适用于按Non-PrimaryKey查找行的情况。可为DataTable创建一个DataView,并通过SortOrder参数指示建立索引。此后使用Find或FindRows查找行。

3.ASP.NET

3.1 减少往返行程(Reduce Round Trips)

使用下面的方法可以减少Web服务器和Browser之间的往返行程:

1.为Browser启用缓存

如果呈现的内容是静态的或变化周期较长,应启用Browser缓存,避免发出冗余的http请求。2.缓冲页面输出

如果可能,则尽量缓冲页面输出,处理结束后再一次传送到客户端,这可以避免频繁传递小块内容所造成的多次网络交互。由于这种方式在页面处理结束之前客户端无法看到页面内容,因此如果一个页面的尺寸较大的话,可考虑使用Response.Flush方法。该方法强制输出迄今为止在缓冲区中的内容,你应当采用合理的算法控制调用Response.Flush方法的次数。

3.使用Server.Transfer重定向请求

使用Server.Transfer方法重定向请求优于Response.Redirect方法。原因是Response.Redirect会向Broswer回送一个响应头,在响应头中指出重定向的URL,之后Brower使用新的URL重新发出请求。而Server.Transfer方法直接是一个简单的服务端调用,完全没有这些开销!

需要注意Server.Transfer有局限性:第一,它会跳过安全检查;第二,只适用于在同一Web应用内的页面间跳转。

3.2 避免阻塞和长时间的作业 如果需要运行阻塞或长时间运行的操作,可以考虑使用异步调用的机制,以便Web服务器能够继续处理其它的请求。

1.使用异步方式调用Web服务和远程对象

只要有可能就要避免在请求的处理过程中对Web服务和远程对象的同步调用,因为它占用的是的ASP.NET 线程池中的工作线程,这将直接影响Web服务器响应其它请求的能力。

2.考虑给不需要返回值的Web方法或远程对象的方法添加OneWay属性

这种模式能让Web Server调用之后就立即返回。可根据实际情况决定是否使用这种方法。

3.使用工作队列

将作业提交到服务器上的工作队列中。客户端通过发送请求来轮询作业的执行结果。

3.3 使用缓存

缓存能在很大程度上决定ASP.NET应用的最终性能。Asp.net支持页面输出缓存和页面部分缓存,并提供Cache API,供应用程序缓存自己的数据。是否使用缓存可考虑下面的要点:

1.识别创建与访问代价较大的数据

2.评估需要缓存数据的易变性

3.评估数据的使用频次

4.将要缓存数据中易变数据和不变数据分离,只缓存不变数据

5.选择合适的缓存机制(除Asp.net Cache外,Application state和Session state也可以作为缓存使用)

3.4 多线程

1.避免在请求处理过程中创建线程

在执行请求的过程中创建线程是一种代价较大的操作,会严重影响Web Server的性能。如果后续的操作必须用线程完成,建议通过thread pool来创建/管理线程。

2.不要依赖线程数据槽或线程静态变量

由于执行请求的线程是ASP.NET thread pool中的工作线程,同一个Client的两次请求不一定由相同的线程来处理。

3.避免阻塞处理请求的线程

参考”避免阻塞和长时间的作业“小节。

4.避免异步调用

这和1的情况类似。异步调用会导致创建新的线程,增加服务器的负担。所以,如果没有并发的作业要执行,就不要执行异步调用。

3.5 系统资源

1.考虑实现资源池以提升性能

2.明确地调用Dispose或Close释放系统资源 3.不要缓存或长时间占用资源池中的资源 4.尽可能晚的申请,尽可能早的释放

3.6 页面处理

1.尽量减小Page的尺寸

包括缩短控件的名称、CSS的class的名称、去掉无谓空行和空格、禁用不需要的ViewState

2.启用页面输出的缓冲区(Buffer)

如果Buffer的机制被关闭,可以用下面的方法打开。

使用程序打开页面输出缓存:

Response.BufferOutput = true;

使用@Page开关打开页面输出缓冲机制:

<%@ Page Buffer = ”true“ %>

使用Web.config或Machine.config配置文件的

节点:

3.利用Page.IsPostBack优化页面输出

4.通过分离页面的不同的内容,来提高缓存效率和减少呈现的时间

5.优化复杂和代价较大的循环

6.合理利用客户端的计算资源,将一些操作转移到客户端进行

3.7 ViewState

ViewState是Asp.net为服务端控件在页面回传之间跟踪状态信息而设计的一种机制。

1.关闭ViewState

如果不需要跟踪页面状态,例如页面不会 回传(PostBack)、不需要处理服务端控件事件或者每次页面刷新时都会重新计算控件内容,那么就不需要用ViewState来记录页面状态了。可以对特定的WebControl设置EnableViewState属性,也可以在页面一级设置:

<%@ Page EnableViewState=”false“ %>

2.在恰当的时间点初始化控件属性

ASP.NET的控件在执行构造函数、初始化的期间设置的属性不会被跟踪变化;而在初始化阶段之后对属性的修改都会被跟踪,并最终记录到IE页面的__VIEWSTATE之中。所以,选择合理的初始化控件属性的执行点,能有效的减小页面尺寸。

3.谨慎选择放到ViewState中的内容

放到ViewState中的内容会被序列化/反序列化,Asp.net为String、Integer、Boolean等基本类型的序列化做了优化,如果Array、ArrayList、HashTable存储的是基本类型效率也较高,但其它类型则需要提供类型转换器(Type Converter),否则将使用代价昂贵的二进制序列化程序。

4.JScript

4.1 JScript性能优化的基本原则

1.尽可能少地减少执行次数。毕竟对解释语言来说,每一个执行步骤,都需要和解释引擎做一次交互。

2.尽可能使用语言内置的功能,比如串链接。

3.尽可能使用系统提供的API来进行优化。因为这些API是编译好的二进制代码,执行效率很高。

4.书写最正确的代码。容错功能是要付出性能代价的。

4.2 JScript语言本身的优化

4.2.1 变量

1.尽量使用局部变量。

因为全局变量其实是全局对象的成员,而局部变量在栈上定义,优先查找,性能相对于全局变量要高。

2.尽量在一个语句中做定义变量和赋值。

3.省略不必要的变量定义。

如果变量的定义可以被一个常量替代,就直接使用常量。

4.使用Object语法对对象赋值。Object的赋值语法在操作复杂对象时效率更高。

例如,可以将下面的代码:

car = new Object();car.make = ”Honda“;car.model = ”Civic“;

car.transmission = ”manual“;car.miles = 100000;

car.condition = ”needs work“;替换成:

car = {

make: ”Honda“, model: ”Civic“,transmission: ”manual“, miles: 100000,condition: ”needs work“ }

4.2.2 对象缓存

1.缓存对象查找的中间结果。

因为JavaScript的解释性,所以a.b.c.d.e,需要进行至少4次查询操作,先检查a再检查a中的b,再检查b中的c,如此往下。所以如果这样的表达式重复出现,只要可能,应该尽量少出现这样的表达式,可以利用局部变量,把它放入一个临时的地方进行查询。

2.缓存创建时间较长的对象。

自定义高级对象和Date、RegExp对象在构造时都会消耗大量时间。如果可以复用,应采用缓存的方式。

4.2.3 字符串操作

1.使用”+=“ 追加字符串,使用”+“来连接字符串。

如果是追加字符串,最好使用s+=anotherStr操作,而不是要使用s=s+anotherStr。

如果要连接多个字符串,应该使用”+“,如:

s+=a;

s+=b;

s+=c;

应该写成

s+=a + b + c;

2.连接大量的字符串,应使用Array的join方法。如果是收集字符串,最好使用JavaScript数组缓存,最后使用join方法连接起来,如下:

var buf = new Array();for(var i = 0;i < 100;i++){

buf.push(i.toString());}

var all = buf.join(”“);

4.2.4 类型转换

1.使用Math.floor()或者Math.round()将浮点数转换成整型。

浮点数转换成整型,这个更容易出错,很多人喜欢使用parseInt(),其实parseInt()是用于将字符串转换成数字,而不是浮点数和整型之间的转换,我们应该使用Math.floor()或者Math.round()。

对象查找中的问题不一样,Math是内部对象,所以Math.floor()其实并没有多少查询方法和调用的时间,速度是最快的。

2.自定义的对象,推荐定义和使用toString()方法来进行类型转换。

对于自定义的对象,如果定义了toString()方法来进行类型转换的话,推荐显式调用toString()。因为内部的操作在尝试所有可能性之后,会尝试对象的toString()方法尝试能否转化为String,所以直接调用这个方法效率会更高。

4.2.5 循环的优化

1.尽可能少使用for(in)循环。

在JavaScript中,我们可以使用for(;;),while(),for(in)三种循环,事实上,这三种循环中for(in)的效率极差,因为他需要查询散列键,只要可以就应该尽量少用。

2.预先计算collection的length。

如:将for(var i = 0;i < collection.length;i++)

替换成:for(var i = 0, len = collection.length;i < len;i++)

效果会更好,尤其是在大循环中。

3.尽量减少循环内的操作。

循环内的每个操作,都会被放大为循环次数的倍数。所以,大循环内微小的改进,在性能的整体提升上都是可观的。

4.使用循环替代递归。

相比循环,递归的效率更差一些。递归的优点是在形式上更自然一些。所以,在不影响代码的维护性的前提下,用循环替代递归。

4.2.6 其它方面

1.尽量使用语言内置的语法。

”var arr = [„];“和”var arr = new Array(„);“是等效的,但是前者的效能优于后者。同样,”var foo = {};“的方式也比”var foo = new Object();“快;”var reg = /../;“要比”var reg=new RegExp()“快。

2.尽量不要使用eval。

使用eval,相当于在运行时再次调用解释引擎,对传入的内容解释运行,需要消耗大量时间。

3.使用prototype代替closure。

使用closure在性能和内存消耗上都是不利的。如果closure使用量过大,这就会成为一个问题。所以,尽量将:

this.methodFoo = function()

替换成:

MyClass.protoype.methodFoo = function()

和closure存在于对象实例之中不同,prototype存在于类中,被该类的所有的对象实例共享。

4.避免使用with语句。

With语句临时扩展对象查找的范围,节省了文字的录入时间,但付出了更多的执行时间。因为每个给出的名称都要在全局范围查找。所以,可以将下面的代码:

with(document.formname){

field1.value = ”one“;field2.value = ”two“;}

变更为:

var form = document.formname;form.field1.value = ”one“;form.field2.value = ”two“;

4.3 DOM相关

4.3.1 创建DOM节点

相比较通过document.write来给页面生成内容,找一个容器元素(比如指定一个div或者span)并设置他们的innerHTML效率更高。而设置innerHTML的方式比通过createElement方法创建节点的效率更高。事实上,设置元素的innerHTML是创建节点效率最高的一种方式。

如果必须使用createElement方法,而如果文档中存在现成的样板节点,应该是用cloneNode()方法。因为使用createElement()方法之后,你需要设置多次元素的属性,使用cloneNode()则可以减少属性的设置次数。同样,如果需要创建很多元素,应该先准备一个样板节点。

4.3.2 离线操作大型的DOM树

在添加一个复杂的DOM树时,可以先构造,构造结束后再将其添加到DOM数的适当节点。这能够节省界面刷新的时间。

同样,在准备编辑一个复杂的树时,可以先将树从DOM树上删除,等编辑结束后再添加回来。

4.3.3 对象查询

使用[”“]查询要比.item()更快。调用.item()增加了一次查询和函数的调用。

4.3.4 定时器

如果针对的是不断运行的代码,不应该使用setTimeout,而应该用setInterval。setTimeout每次要重新设置一个定时器。

4.4 其他

1.尽量减小文件尺寸。

将JScript文件中无关的空行、空格、注释去掉,有助于减小JS文件的尺寸,提高下载的时间。(可以通过工具来支持代码发布)

2.尽量不要在同一个Page内同时引用JScript和VBScript引擎

3.将Page内的JScript移入到单独的JS文件中。

4.将Page内的JScript放置在Page的最下面,有助于提高页面的响应速度。

5.利用cache,减少JScript文件的下载次数

6.在HTML内书写JScript文件的URL时,注意统一大小写。这样可以利用前面URL缓存的文件。

C# 性能优化——三种字符串拼接效率

2011年04月07日 星期四 17:56 字符串拼接主要包括三类:+,String.Format(),StringBuilder.Append()1)对于少量固定的字符串拼接,如string s= ”a“ + ”b“ + ”c“,系统会优化成s= String.Concat(”a“,”b“,”c“),不会新建多个字符串。

如果写成string s=”a“;s +=”b“;s+=”c“;则会创建三个新的字符串。2)String.Format的源代码: public static String Format(IFormatProvider provider, String format, params Object[] args){ if(format == null || args == null)throw new ArgumentNullException((format==null)?”format“:”args“);StringBuilder sb = new StringBuilder(format.Length + args.Length * 8);sb.AppendFormat(provider,format,args);return sb.ToString();} 可见,它和StringBuilder有着相似的效率,比用“+”的拼接方式高效,并且代码易于阅读。

string s= String.Format(”{0}{1}{2}“,”a“,”b“,”c");3)StringBuilder可以指定内存空间的容量,但可能需要进行数据类型转化。字符串较少时,可以使用String.Format()代替。

4)少量的字符串操作时,可以使用“+”或者String.Format();大量的字符串操作时,比如在循环体内,必须使用StringBuilder.Append()。

下载总结使用Unity3D优化游戏运行性能的经验word格式文档
下载总结使用Unity3D优化游戏运行性能的经验.doc
将本文档下载到自己电脑,方便修改和收藏,请勿使用迅雷等下载。
点此处下载文档

文档为doc格式


声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:645879355@qq.com 进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。

相关范文推荐

    (敏捷开发).NET性能优化方面的总结.docx

    (敏捷开发).NET性能优化方面的总结 一、SqlDataRead和Dataset的选择 Sqldataread优点:读取数据非常快。如果对返回的数据不需做大量处理的情况下,建议使用SqlDataReader,其性能......

    锅炉运行经验小总结[合集5篇]

    1.锅炉低负荷运行时应注意些什么? (1)低负荷时应尽可能燃用挥发分较高的煤。当燃烧挥发分较低、燃烧不稳时,应投入点火枪助燃,以防止可能出现灭火。 (2)低负荷时投入的燃烧器应较......

    锅炉运行中总结的一些经验

    锅炉运行中总结的一些经验 任建祖 对于运行人员,一个已经正常运行多年的热电厂,总的来说,锅炉运行包括几个方面:日常监视与调整操作;事故处理;启动与停止;优化运行方式与技术改进。......

    汽轮机组运行的优化要点总结

    我班组运行的优化要点总结 如何使各种参数及辅机运行方式最接近理想的状态,即是我班组优化运行的目标。机组的优化运行是对运行机组性能的在线技术分析,及时发现问题,及时进行......

    有关水晶报表的使用经验和总结(范文模版)

    有关水晶报表的使用经验和总结 Asp.Net(webform)中使用水晶报表 这篇文章教你如何在.Net Web应用中使用水晶报表,也可以让你在学习过程中少走一些弯路。为了得到最好的效果,......

    Word 2010 使用技巧总结(个人经验)

    Word 2010 使用技巧总结(个人经验) 核心提示:在Word2010文档中,利用自选图形库提供的丰富的流程图形状和连接符可以制作各种用途的流程图,制作步骤如下所述 在Word2010文档中,利用......

    上海seo总结的网站优化经验

    上海seo总结的网站优化经验 一、一个网站刚开始实行seo时可先做一个关键词。等这个关键词到了一定的排名的时候再做其他的词。二、一个网站刚开始实行seo应先把内链和title......

    部编新教材使用经验及问题总结

    部编新教材使用经验及问题总结 部编版一年级语文新教材的编排有了很大的改变,不仅关注基础知识和基础技能,还关注学生的情感、态度和价值观。不仅提高了学生的学习兴趣,也让我......