ASP.NET站点性能提升-CPU
时间:2010-11-18 来源:9527
- 重点关注频繁执行的代码。循环,特别是嵌套循环,需要特别注意。如果对使用继承自IComparable类进行排序,这段代码会执行得非常频繁。如果从数据库获取数据,每一行数据都要处理,这也会使用到循环。
- 可以使用轻量级的计数器,测量执行的频率和执行的时间。
- 进行老式的调试。对网站进行压力测试,这样会使用很多CPU。减少一半代码,如果CPU使用率减少很多,那么问题就可能出在减少的那一半代码。继续,直到找到有问题的代码。
- 尝试改变算法。
测算代码执行的时间,可以使用stopWatch类:
using System.Diagnostics; Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); // first bit of code ... stopWatch.Stop(); TimeSpan elapsedTime1 = stopWatch.Elapsed; stopWatch.Reset(); stopWatch.Start(); // second bit of code ... stopWatch.Stop(); TimeSpan elapsedTime2 = stopWatch.Elapsed;
工具
有时,瓶颈隐藏的很深,这就需要使用分析工具。这些工具会降低网站的运行速度,所以在开发环境,而不要在生产环境使用它们。
- Visual Studio Performance Profilling:它可以分析出每个方法占用的内存、平均执行时间、调用频率。另一个很棒的功能是Hotpathing,显示哪段代码在执行时占用较多的时间。完整文档:http://msdn.microsoft.com/en-us/library/z9z62c29.aspx。
- ANTS Performance Profiler: http://www.red-gate.com/products/ants_performance_profiler/。
- Eqatec Profiler:http://www.eqatec.com/tools/profiler/。
- Slimtune:http://code.google.com/p/slimtune/。
数据访问
连接池
打开数据库连接是一个昂贵的的操作。所以ASP.NET维护了一个连接池。每一个连接字符串都有一个连接池。
当打开一个连接时,从连接池得到一个连接。当关闭那个连接时,它依然是打开的,归还连接池,准备给另一个线程使用。
为每一个需要访问的数据库,只使用一个连接字符串。
在web.config中存储连接字符串:
<configuration> <connectionStrings> <add name="ConnectionString" connectionString="....."/> </connectionStrings> </configuration>
在代码中获取连接字符串:
using System.Configuration; ... string connectionString = ConfigurationManager.ConnectionStrings[ "ConnectionString"].ConnectionString;
连接池由连接字符串参数控制:
Max Pool Size:连接池的最大值。默认值是100。如果连接池中的连接达到最大值,连接请求将会排队。
Min Pool Size:当连接池创建时连接的初始数量。默认值是0。这样,最初的数据库访问会因为创建新连接被延迟。为了減少这些延迟,可以设置参数为预计的程序最终需要的连接数量。
Connect Timeout:在终止并产生错误前请求等待连接的时间,单位是秒。默认值是15秒。
Pooling:是否使用连接池。默认值是true。设置为false关闭连接池。
为什么要关闭连接池?是为了在应用程序发生连接泄露时,发生崩溃。
考虑以下代码:
SqlConnection connection = new SqlConnection(connectionString); connection.Open(); ... code that may throw an exception ... connection.Close();
如果代码抛出异常,连接就不会关闭。最终,它会被垃圾收集器关闭,但这会需要一段时间,这要看内存压力。同时,越来越多的连接会加到连接池中。
当达到连接池的最大值时,请求会等待连接,直到超时,最终被终止。
最好的方法是修改代码,这样即使在有异常的情况下,连接也会关闭:
using (SqlConnection connection = new SqlConnection(connectionString)) { connection.Open(); ... code that may throw an exception ... }
使用计数器观察当前打开的连接数量,如果有流量稳定的情况下,连接数量保持增长,说明可能有连接泄露。
分类:SQL Server General Statistics
User Connections:当前连接到系统的用户数。
DataSet和List
当从数据库中读取数据时,可能需要将数据存储在集合中。这样,就可以缓存或者将它传递到另一层。
最简单的集合是使用DataSet,只需使用少量代码就可以填充DataSet,并且DataSet有很多内置的功能。另一种选择是泛型List,这需要自己填充,也没有很多内置功能,但它是轻量级的。
如果不需要DataSet提供的功能,使用泛型List可以节省可见的CPU周期。
返回多结果集
ADO.NET允许一次获取多个结果集。例如:
CREATE PROCEDURE [dbo].[GetBooksAndAuthors] AS BEGIN SET NOCOUNT ON; SELECT [BookId] ,[Title] ,[AuthorId] ,[Price] FROM [dbo].[Book] SELECT [AuthorId] ,[Name] ,[Address] ,[Phone] ,[Email] FROM [dbo].[Author] END
使用SqlDataReader.NextResult访问第二个结果集:
using (SqlDataReader reader = cmd.ExecuteReader()) { while (reader.Read()) { // read first result set ... } reader.NextResult(); while (reader.Read()) { // read second result set ... } }
如果有一个页面上需要显示多个结果集,可一次读取它们。
一次连接中发送多个Insert语句
在批量插入数据时,可以一次执行多条Insert语句。
创建SQL参数:
const string singleExec = "EXEC dbo.InsertData @Title{0}, @Author{0}, @Price{0};"; StringBuilder sql = new StringBuilder(); for (int j = 0; j < nbrInserts; j++) { sql.AppendFormat(singleExec, j); }
为参数赋值:
for (int j = 0; j < nbrInserts; j++) { cmd.Parameters.AddWithValue("@Title" + j.ToString(), ...); cmd.Parameters.AddWithValue("@Author" + j.ToString(), ...); cmd.Parameters.AddWithValue("@Price" + j.ToString(), ...); } Then send all EXEC statements in one go to the database: cmd.CommandType = CommandType.Text; cmd.ExecuteNonQuery();
注意command text包括SQL文本(EXEC语句),而不是存储过程名。所以使用CommandType.Text。
使用native data providers
使用native data providers,例如System.Data.SqlClient或者System.Data.OracleClient,而不要使用System.Data.OleDb和System.Data.ODBC,因为native data providers较少抽象,所以效率更高,
异常
抛出异常是非常昂贵的。当你抛出一个异常,首先需要在堆上创建异常对象。异常对象必需包括调用堆栈,这由运行时创建。然后,运行时查找合适的异常处理器,执行它。最后,还要执行finally代码块。只在真正出现异常的情况下使用异常,不要在正常程序流程中。
计数器
分类:NET CLR Exceptions
# of Exceps Thrown:从程序启动开始抛出的异常数量。
# of Exceps Thrown/sec:每秒抛出的异常数量。
# of Filters/sec:每秒执行的.NET异常过滤器数量。异常过滤器判断是否应该处理异常。
# of Finallys/sec:每秒执行的finally程序块。
Throw To Catch Depth/sec:每秒从抛出异常的帧到处理异常的帧间的堆栈帧。
DataBinder.Eval
例如GridView和Repeater的控件使用模板。常用的方法是在模板中使用DataBinder.Eval引用字段:
<asp:Repeater ID="rptrEval" runat="server"> <ItemTemplate> <%# Eval("field1")%> <%# Eval("field2")%> <%# Eval("field3")%> <%# Eval("field4")%> </ItemTemplate> </asp:Repeater>
这种方法使用反射。如果有10行,每行4个字段,就执行了40次Eval。
更快的方法是直接引用包含字段的类:
<asp:Repeater ID="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。
垃圾收集
使用.NET CLR Memmory分类中的% Time in GC计算器可以查看垃圾收集占用的时间。
线程
如果滥用线程,可能会浪费很多CPU周期。如果你的程序是多线程的,考虑以下方法减少线程耗费:
- 如果使用线程等待数据库或web service之类的资源,可能使用异步请求。
- 不要自己创建线程。使用ThreadPool.QueueUserWorkItem从线程池中获得线程。
- 不要使用线程进行CPU密集型的任务。如果你有4个CPU,同时进行4个以上的CPU密集型任务是没有意义的。
- 減少同时运行的线程数量。线程切换很昂贵。
使用以下计数器获取正在运行的线程信息:
分类:Thread
% Processor Time:显示每个线程使用的处理器时间百分比。
Context Switches/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管道
如果不需要一些HTTP modules,可以在web.config中移除它们。例如:
<system.web> <httpModules> <remove name="RoleManager" /> </httpModules> </system.web>
更多资源
- 10 Tips for Writing High-Performance Web Applications:http://msdn.microsoft.com/en-us/magazine/cc163854.aspx?ppud=4。
- Improving ASP.NET Performance:http://msdn.microsoft.com/en-us/library/ms998549.aspx。
- Base Class Library Performance Tips and Tricks:http://msdn.microsoft.com/en-us/magazine/cc163670.aspx。