性能微调提示
任何编程模型都有常见的性能缺陷,ASP.NET 也不例外。本节描述一些可避免在代码中出现性能瓶颈的方法。
1.在未使用时禁用会话状态: 并非所有的应用程序或页都要求基于每个用户的会话状态。如果不需要,可将其完全禁用。这可以通过以下页级别指令轻松实现:
<%@ Page EnableSessionState="false" %>
注意:如果页需要访问会话变量但不创建或修改它们,请将指令值设置为 ReadOnly。还可为 XML Web 服务方法禁用会话状态。请参阅 XML Web 服务一节中的使用对象和内部对象。
2.慎重选择会话状态提供程序: ASP.NET 为存储应用程序的会话数据提供了三种不同的方法:进程内会话状态、作为 Windows 服务的进程外会话状态和 SQL 数据库中的进程外会话状态。每种方法都有自己的优点,但进程内会话状态是迄今为止速度最快的解决方案。如果仅在会话状态中存储少量易失数据,则应使用进程内提供程序。进程外解决方案主要用于 Web 花园和 Web 农场方案,或用于当服务器/进程重新启动时不能丢失数据的情况。
3.避免与服务器间的过多往返行程:Web 窗体页框架是 ASP.NET 的最佳功能之一,因为它可以显著减少为完成某项任务所需编写的代码量。使用服务器控件和回发事件处理模型的页元素编程访问无疑是最省时的功能。但是,对这些功能的使用存在着适当和不适当的方法,了解何时使用它们是适当的很重要。
应用程序通常仅在检索数据或存储数据时才需要往返于服务器。多数数据操作可在往返行程间在客户端进行。例如在用户提交数据前,通常可以在客户端验证窗体项。通常,如果不需要将信息中继回服务器,则不应往返于服务器。
如果编写自己的服务器控件,请考虑让它们为上级(支持 ECMAScript)浏览器呈现客户端代码。通过采用“智能”控件,可显著减少对 Web 服务器的不必要点击次数。
4.使用 Page.IsPostback 避免往返行程上的额外工作:如果处理服务器控件回发,通常需要在第一次请求页时执行代码,该代码不同于激发事件时用于往返行程的代码。如果检查 Page.IsPostBack 属性,则代码可按条件执行,具体取决于是否有对页的初始请求或对服务器控件事件的响应。这样做似乎很明显,但实际上可以忽略此项检查而不更改页的行为。例如:
<script language="C#" runat="server">
public DataSet ds;
...
void Page_Load(Object sender, EventArgs e) {
// ...set up a connection and command here...
if (!Page.IsPostBack) {
String query = "select * from Authors where FirstName like '%JUSTIN%'";
myCommand.Fill(ds, "Authors");
myDataGrid.DataBind();
}
}
void Button_Click(Object sender, EventArgs e) {
String query = "select * from Authors where FirstName like '%BRAD%'";
myCommand.Fill(ds, "Authors");
myDataGrid.DataBind();
}
</script>
<form runat="server">
<asp:datagrid datasource='<%# ds.DefaultView %>' runat="server"/><br>
<asp:button onclick="Button_Click" runat="server"/>
</form>
<script language="VB" runat="server">
Public ds As DataSet
...
Sub Page_Load(sender As Object, e As EventArgs)
' ...set up a connection and command here...
If Not (Page.IsPostBack)
Dim query As String = "select * from Authors where FirstName like '%JUSTIN%'"
myCommand.Fill(ds, "Authors")
myDataGrid.DataBind()
End If
End Sub
Sub Button_Click(sender As Object, e As EventArgs)
Dim query As String = "select * from Authors where FirstName like '%BRAD%'"
myCommand.Fill(ds, "Authors")
myDataGrid.DataBind()
End Sub
</script>
<form runat="server">
<asp:datagrid datasource='<%# ds.Tables["Authors"].DefaultView %>' runat="server"/><br>
<asp:button onclick="Button_Click" runat="server"/>
</form>
<script language="JScript" runat="server">
public var ds:DataSet;
...
function Page_Load(sender:Object, e:EventArgs) : void {
// ...set up a connection and command here...
if (!Page.IsPostBack) {
var query:String = "select * from Authors where FirstName like '%JUSTIN%'";
myCommand.Fill(ds, "Authors");
myDataGrid.DataBind();
}
}
function Button_Click(sender:Object, e:EventArgs) : void {
var query:String = "select * from Authors where FirstName like '%BRAD%'";
myCommand.Fill(ds, "Authors");
myDataGrid.DataBind();
}
</script>
<form runat="server">
<asp:datagrid datasource='<%# ds.DefaultView %>' runat="server"/><br>
<asp:button onclick="Button_Click" runat="server"/>
</form>
Page_Load 事件对所有请求都执行,因此检查了 Page.IsPostBack,以便在处理 Button_Click 事件回发时不执行第一个查询。请注意,即使没有此检查,页的行为也不会改变,因为第一个查询中的绑定会被事件处理程序中的 DataBind 调用推翻。记住,在编写页时会很容易忽略这个简单的性能改进。
3.谨慎适当地使用服务器控件:尽管服务器控件使用起来非常容易,但它并不总是最佳选择。许多情况下,简单的呈现或数据绑定替换可以完成同样的事情。例如:
<script language="C#" runat="server">
public String imagePath;
void Page_Load(Object sender, EventArgs e) {
//...retrieve data for imagePath here...
DataBind();
}
</script>
<%-- the span and img server controls are unecessary...--%>
The path to the image is: <span innerhtml='<%# imagePath %>' runat="server"/><br>
<img src='<%# imagePath %>' runat="server"/>
在此示例中,不需要服务器控件将值代入发送回客户端的结果 HTML。在许多其他情况下此方法同样适用,甚至在服务器控件模板中。但是,如果要以编程方式操作控件属性、从中处理事件或利用其状态保存,则服务器控件更合适。应检查服务器控件的使用,并查找可优化的代码。
6.避免过多的服务器控件视图状态:自动状态管理是一种功能,它使服务器控件能够在往返行程中重新填充它们的值,而不要求编写任何代码。但是,此功能并不能任意使用,因为控件状态是在隐藏的窗体字段中传入和传出服务器的。应当明白 ViewState 何时有帮助,何时没有。例如,如果在每个往返行程中将控件绑定到数据(如第四条提示中的数据网格示例所示),则不要求控件维护它的视图状态,因为无论如何都将擦除任何重新填充的数据。
默认情况下,为所有的服务器控件启用 ViewState。若要禁用它,请将控件的 EnableViewState 属性设置为 false,如下例所示:
<asp:datagrid EnableViewState="false" datasource="..." runat="server"/>
还可在页级别关闭 ViewState。这在根本不从页回发时非常有用,如下例所示:
<%@ Page EnableViewState="false" %>
注意,User Control 指令也支持此属性。若要分析页上的服务器控件使用的视图状态量,请启用跟踪并查看“控件层次结构”表中的“视图状态”列。有关跟踪功能及如何启用它的更多信息,请参阅应用程序级别的跟踪记录功能。
7.对字符串连接使用 Response.Write:在页面或用户控件中对字符串连接使用 HttpResponse.Write 方法。该方法提供非常有效的缓冲和连接服务。但是,如果您打算执行大量的连接,则使用下列示例中的方法(即多次调用 Response.Write)来连接字符串比仅调用一次 Response.Write 方法要快。
Response.Write("a");
Response.Write(myString);
Response.Write("b");
Response.Write(myObj.ToString());
Response.Write("c");
Response.Write(myString2);
Response.Write("d");
8.不要依赖代码中的异常:异常非常浪费资源,应在代码中尽量避免。绝不要将异常作为控制常规程序流的方法。如果可以在代码中检测到会导致异常的条件,就应该那样做,而不要等到捕捉异常后再处理该条件。常见的方案包括:检查空值,分配给将分析为数字值的字符串,或在应用数学运算前检查特定值。例如:
// Consider changing this:
try {
result = 100 / num;
}
catch (Exception e) {
result = 0;
}
// To this::
if (num != 0)
result = 100 / num;
else
result = 0;
9.
在 Visual Basic 或 JScript 代码中使用早期绑定:Visual Basic、VBScript 和 JScript 的优点之一是它们无类型的特性。只需使用它们即可创建变量,并不需要显式的类型声明。从一种类型分配到另一种类型时,同样会自动执行转换。这既是优点也是缺点,因为就性能而言,晚期绑定虽很方便但很昂贵。
Visual Basic 语言现在通过使用特殊的 Option Strict 编译器指令来支持类型安全编程。为了向后兼容,默认情况下 ASP.NET 不启用 Option Strict。但为了获得最佳性能,应在页上使用 Strict 属性或使用 Control 指令,为页启用 Option Strict:
<%@ Page Language="VB" Strict="true" %>
<%
Dim B
Dim C As String
' This causes a compiler error:
A = "Hello"
' This causes a compiler error:
B = "World"
' This does not:
C = "!!!!!!"
' But this does:
C = 0
%>
JScript 也支持无类型编程,但它不提供强制早期绑定的编译器指令。满足以下条件的变量是后期绑定的:
被显式声明为对象。
是无类型声明的类的字段。
是无显式类型声明的专用函数/方法成员,并且无法从其使用推断出类型。
最后一个特点非常复杂。如果 JScript 编译器能够根据使用变量的方式推断出其类型就会进行优化。在下面的示例中,变量 A 为早期绑定,而变量 B 为后期绑定:
var A;
var B;
A = "Hello";
B = "World";
B = 0;
为获得最佳性能,请将 JScript 变量声明为具有类型。例如,“var A : String”。
10.
将大量调用的 COM 组件移植为托管代码:.NET 框架一种非常容易的方法与传统的 COM 组件相互操作。其优点是可以在保留现有代码的同时利用新的平台。但是,在某些情况下,保留旧组件的性能成本超出了将组件迁移到托管代码的费用。每种情况都非常特别,而决定所需更改的内容的最佳方法是测量站点的性能。但是,通常 COM 交互性的性能影响与函数调用的次数或从非托管代码封送到托管代码的数据量成比例。由于各层间的通讯数,需要同大量调用交互的组件称为“chatty”。应考虑将这种组件移植为完全托管的代码,以从 .NET 平台提供的性能收益中获益。或者可以考虑重新设计组件,以请求更少的调用或一次封送更多数据。
11.
将 SQL 存储过程用于数据访问:在 .NET 框架提供的所有数据访问方法中,基于 SQL 的数据访问是生成性能最好的可缩放 Web 应用程序的最佳选择。使用托管 SQL 提供程序时,可通过使用编译的存储过程而不是特殊查询,获得额外的性能提高。有关使用 SQL 存储过程的示例,请参考本教程的服务器端的数据访问一节。
12.
使用 SqlDataReader 获得快进只读数据游标:SqlDataReader 对象对从 SQL 数据库中检索的数据提供前进只读游标。如果 SqlDataReader 适合于您的情况,则它是一个比 DataSet 更好的选择。因为 SqlDataReader 支持 IEnumerable 接口,甚至还可以绑定服务器控件。有关使用 SqlDataReader 的示例,请参阅本教程的服务器端数据访问一节。
13.
尽可能缓存数据和输出:ASP.NET 编程模型提供了一个简单的机制,在不需要为每个请求动态计算页输出或数据时缓存它们。在设计页时可以考虑用缓存来优化应用程序中那些预期有最大通信量的地方。适当地使用缓存可增强站点的性能,有时甚至可以增大一个数量级或更多,这是 .NET 框架的任何其他功能无法企及的。有关如何使用缓存的更多信息,请参阅本教程的缓存服务一节。
14.
为多处理器计算机启用 Web 花园:ASP.NET 进程模型帮助在多处理器计算机上启用可缩放性,将工作分发给多个进程(每个 CPU 一个),并且每个进程都将处理器关系设置为其 CPU。该技术称为 Web 园艺,它可以显著提高某些应用程序的性能。若要了解如何启用 Web 园艺,请参考使用进程模型一节。
15.
不要忘记禁用调试模式:ASP.NET 配置中的 <compilation> 节控制应用程序是否在调试模式中编译。调试模式严重降低性能。在部署生产应用程序或测量性能之前,始终记住禁用调试模式。有关调试模式的更多信息,请参考题为 SDK 调试器的章节。