您的位置:首页 > 编程语言 > ASP

ASP.NET站点性能提升-CPU

2010-11-18 17:30 453 查看

识别瓶颈

有多项技术可以在识别高CPU占用率代码:

重点关注频繁执行的代码。循环,特别是嵌套循环,需要特别注意。如果对使用继承自IComparable类进行排序,这段代码会执行得非常频繁。如果从数据库获取数据,每一行数据都要处理,这也会使用到循环。可以使用轻量级的计数器,测量执行的频率和执行的时间。进行老式的调试。对网站进行压力测试,这样会使用很多CPU。减少一半代码,如果CPU使用率减少很多,那么问题就可能出在减少的那一半代码。继续,直到找到有问题的代码。尝试改变算法。
测算代码执行的时间,可以使用stopWatch类:

usingSystem.Diagnostics; StopwatchstopWatch=newStopwatch(); stopWatch.Start(); //firstbitofcode... stopWatch.Stop(); TimeSpanelapsedTime1=stopWatch.Elapsed; stopWatch.Reset(); stopWatch.Start(); //secondbitofcode... stopWatch.Stop(); TimeSpanelapsedTime2=stopWatch.Elapsed;

工具

有时,瓶颈隐藏的很深,这就需要使用分析工具。这些工具会降低网站的运行速度,所以在开发环境,而不要在生产环境使用它们。

VisualStudioPerformanceProfilling:它可以分析出每个方法占用的内存、平均执行时间、调用频率。另一个很棒的功能是Hotpathing,显示哪段代码在执行时占用较多的时间。完整文档:http://msdn.microsoft.com/en-us/library/z9z62c29.aspx。
ANTSPerformanceProfiler:http://www.red-gate.com/products/ants_performance_profiler/。
EqatecProfiler:http://www.eqatec.com/tools/profiler/。
Slimtune:http://code.google.com/p/slimtune/。

数据访问

连接池

打开数据库连接是一个昂贵的的操作。所以ASP.NET维护了一个连接池。每一个连接字符串都有一个连接池。

当打开一个连接时,从连接池得到一个连接。当关闭那个连接时,它依然是打开的,归还连接池,准备给另一个线程使用。

为每一个需要访问的数据库,只使用一个连接字符串。

在web.config中存储连接字符串:

<configuration> <connectionStrings> <addname="ConnectionString"connectionString="....."/> </connectionStrings> </configuration>
在代码中获取连接字符串:

usingSystem.Configuration; ... stringconnectionString= ConfigurationManager.ConnectionStrings[ "ConnectionString"].ConnectionString;
连接池由连接字符串参数控制:

MaxPoolSize:连接池的最大值。默认值是100。如果连接池中的连接达到最大值,连接请求将会排队。

MinPoolSize:当连接池创建时连接的初始数量。默认值是0。这样,最初的数据库访问会因为创建新连接被延迟。为了減少这些延迟,可以设置参数为预计的程序最终需要的连接数量。

ConnectTimeout:在终止并产生错误前请求等待连接的时间,单位是秒。默认值是15秒。

Pooling:是否使用连接池。默认值是true。设置为false关闭连接池。

为什么要关闭连接池?是为了在应用程序发生连接泄露时,发生崩溃。

考虑以下代码:

SqlConnectionconnection=newSqlConnection(connectionString); connection.Open(); ...codethatmaythrowanexception... connection.Close();
如果代码抛出异常,连接就不会关闭。最终,它会被垃圾收集器关闭,但这会需要一段时间,这要看内存压力。同时,越来越多的连接会加到连接池中。

当达到连接池的最大值时,请求会等待连接,直到超时,最终被终止。

最好的方法是修改代码,这样即使在有异常的情况下,连接也会关闭:

using(SqlConnectionconnection=new SqlConnection(connectionString)) { connection.Open(); ...codethatmaythrowanexception... }使用计数器观察当前打开的连接数量,如果有流量稳定的情况下,连接数量保持增长,说明可能有连接泄露。分类:SQLServerGeneralStatisticsUserConnections:当前连接到系统的用户数。

DataSet和List

当从数据库中读取数据时,可能需要将数据存储在集合中。这样,就可以缓存或者将它传递到另一层。

最简单的集合是使用DataSet,只需使用少量代码就可以填充DataSet,并且DataSet有很多内置的功能。另一种选择是泛型List,这需要自己填充,也没有很多内置功能,但它是轻量级的。

如果不需要DataSet提供的功能,使用泛型List可以节省可见的CPU周期。

返回多结果集

ADO.NET允许一次获取多个结果集。例如:

CREATEPROCEDURE[dbo].[GetBooksAndAuthors] AS BEGIN SETNOCOUNTON; SELECT[BookId] ,[Title] ,[AuthorId] ,[Price] FROM[dbo].[Book] SELECT[AuthorId] ,[Name] ,[Address] ,[Phone] ,[Email] FROM[dbo].[Author] END使用SqlDataReader.NextResult访问第二个结果集:using(SqlDataReaderreader=cmd.ExecuteReader())
{
while(reader.Read())
{
//readfirstresultset...
}
reader.NextResult();
while(reader.Read())
{
//readsecondresultset...
}
}如果有一个页面上需要显示多个结果集,可一次读取它们。

一次连接中发送多个Insert语句

在批量插入数据时,可以一次执行多条Insert语句。

创建SQL参数:

conststringsingleExec="EXECdbo.InsertData@Title{0},@Author{0},@Price{0};";
StringBuildersql=newStringBuilder();
for(intj=0;j<nbrInserts;j++)
{
sql.AppendFormat(singleExec,j);
}为参数赋值:for(intj=0;j<nbrInserts;j++)
{
cmd.Parameters.AddWithValue("@Title"+j.ToString(),...);
cmd.Parameters.AddWithValue("@Author"+j.ToString(),...);
cmd.Parameters.AddWithValue("@Price"+j.ToString(),...);
}
ThensendallEXECstatementsinonegotothedatabase:
cmd.CommandType=CommandType.Text;
cmd.ExecuteNonQuery();
注意commandtext包括SQL文本(EXEC语句),而不是存储过程名。所以使用CommandType.Text。

使用nativedataproviders

使用nativedataproviders,例如System.Data.SqlClient或者System.Data.OracleClient,而不要使用System.Data.OleDb和System.Data.ODBC,因为nativedataproviders较少抽象,所以效率更高,

异常

抛出异常是非常昂贵的。当你抛出一个异常,首先需要在堆上创建异常对象。异常对象必需包括调用堆栈,这由运行时创建。然后,运行时查找合适的异常处理器,执行它。最后,还要执行finally代码块。只在真正出现异常的情况下使用异常,不要在正常程序流程中。

计数器

分类:NETCLRExceptions

#ofExcepsThrown:从程序启动开始抛出的异常数量。

#ofExcepsThrown/sec:每秒抛出的异常数量。

#ofFilters/sec:每秒执行的.NET异常过滤器数量。异常过滤器判断是否应该处理异常。

#ofFinallys/sec:每秒执行的finally程序块。

ThrowToCatchDepth/sec:每秒从抛出异常的帧到处理异常的帧间的堆栈帧。

DataBinder.Eval

例如GridView和Repeater的控件使用模板。常用的方法是在模板中使用DataBinder.Eval引用字段:

<asp:RepeaterID="rptrEval"runat="server">
<ItemTemplate>
<%#Eval("field1")%>
<%#Eval("field2")%>
<%#Eval("field3")%>
<%#Eval("field4")%>
</ItemTemplate>
</asp:Repeater>
这种方法使用反射。如果有10行,每行4个字段,就执行了40次Eval。

更快的方法是直接引用包含字段的类:

<asp:RepeaterID="rptrCast"runat="server">
<ItemTemplate>
<%#((MyClass)Container.DataItem).field1%>
<%#((MyClass)Container.DataItem).field2%>
<%#((MyClass)Container.DataItem).field3%>
<%#((MyClass)Container.DataItem).field4%>
</ItemTemplate>
</asp:Repeater>

如果使用DataTable,强制转换成DataRowView。

垃圾收集

使用.NETCLRMemmory分类中的%TimeinGC计算器可以查看垃圾收集占用的时间。

线程

如果滥用线程,可能会浪费很多CPU周期。如果你的程序是多线程的,考虑以下方法减少线程耗费:

如果使用线程等待数据库或webservice之类的资源,可能使用异步请求。
不要自己创建线程。使用ThreadPool.QueueUserWorkItem从线程池中获得线程。
不要使用线程进行CPU密集型的任务。如果你有4个CPU,同时进行4个以上的CPU密集型任务是没有意义的。
減少同时运行的线程数量。线程切换很昂贵。

使用以下计数器获取正在运行的线程信息:

分类:Thread

%ProcessorTime:显示每个线程使用的处理器时间百分比。

ContextSwitches/sec:显示每个线程每秒的上下文切换比率。

StringBuilder

参考内存优化中StringBuilder使用。

正则表达式初始化

当使用正则表达式匹配字符串时,有两种方案。方案一,可以首先使用正则表达式初始化Regex对象,然后使用这个对象的IsMatch方法。方案二,直接调用静态方法Regex.IsMatch。

如果需要进行多次相同的匹配,最好在循环外初始化Regex对象,而不要使用IsMatch的静态版本。

如果网站需要很多次匹配正则表达式,你可以创建一个包含这正则表达式对象的静态类。这样,就只在程序启动时初始化,而不是在每次请求时都要初始化。

UtcNow

如果只是比较日期,而不用显示给访问者,DateTime.UtcNow要比DateTime.Now快得多。

Foreach

C#语句foreach是一种遍历集合的便利方法,但因为使用enumerator,它要比通常的for循环要慢。

虚属性

如果定义类中的属性为virtual,就是允许派生类重写这个属性。但是,这样会减慢属性访问,因为编译器会不再内联它。

所以,如果你读虚属性多次,你可以将属性拷贝到本地变量中,这样可以提高性能。

避免不必要的处理

早验证。确保在处理数据之前,数据是正确的。

在进行昂贵操作前,检查用户是否还在线。使用Response.IsClientConnected检查。

检查Page.IsPostBack,这样就不会重新生成已经在ViewState中的数据了。

缩短HTTP管道

如果不需要一些HTTPmodules,可以在web.config中移除它们。例如:

<system.web>
<httpModules>
<removename="RoleManager"/>
</httpModules>
</system.web>

更多资源

10TipsforWritingHigh-PerformanceWebApplications:http://msdn.microsoft.com/en-us/magazine/cc163854.aspx?ppud=4。

ImprovingASP.NETPerformance:http://msdn.microsoft.com/en-us/library/ms998549.aspx。

BaseClassLibraryPerformanceTipsandTricks:http://msdn.microsoft.com/en-us/magazine/cc163670.aspx。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: