视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
ASP.NET页面优化 性能提升8倍的方法
2020-11-27 22:42:26 责编:小采
文档

为了让您对优化的效果有个直观的了解,我准备了下面的测试结果截图:

测试环境:
1. Windows Server 2003 SP2
2. Viaual Studio 2008,使用自带的WebDev.WebServer.EXE运行网站程序。
3. (ThinkPad SL510):Core2 T6670 2.2GHz, 4G内存

二个红框中的数字反映了优化前后的执行时间。

数字表明:优化前后,执行时间有了8倍多的差别。

测试背景

看过了优化结果,再来介绍一下:这个测试到底是在测试什么东西?

现在有很多做ASP.NET的开发人员,应该都是从ASP.NET的WebForm编程模型开始学习的。大家都很喜欢用服务器控件,不管输出什么,都会使用服务器控件。有时候为了让页面呈现干净的HTML代码,有些人会选择使用Repeater,Literal这类简单的服务器控件。或许有些人认为:我已不使用GridView这样强大复杂的控件,页面执行速度已经很快了。

真是这样吗?

今天测试的起点就从使用简单的服务器开始,我会分二次对它做一系列的性能优化。
最终就是上图中的3个结果,它们反映了二次优化的改进过程。

在继续介绍之前,有一点我想有必要说明一下:

优化的过程涉及到ASP.NET服务器控件的使用,测试结果也仅仅只是一个参考数字。
如果您认为您的开发工作非常依赖于服务器控件的使用,
那么测试结果对您来说其实是无意义的,请不要在意这个结果。
测试方法
在这次优化过程中,我并没有设计很复杂的测试页面,而是一个很简单的测试页面,页面显示效果如下:

这个页面其实就是显示了一堆超链接,它们来自于我的博客侧边栏的【推荐排行榜】,总共有20条记录,我让页面重复5次输出,也就是生成了100个超链接。

测试的数据是这样获取的:
我复制了我的博客侧边栏的【推荐排行榜】的那段HTML代码,保存到一个文件中:

然后,网站在初始化时,从这段HTML代码提取链接地址以及显示文字,保存到一个BlogInfo的列表中,代码如下:
代码如下:


public class BlogInfo
{
public string Title;
public string Href;
}

public static class XmlDb
{
public static List<BlogInfo> Blogs { get; private set; }


public static void LoadBlogs()
{
string filePath = Path.Combine(HttpRuntime.AppDomainAppPath, @"App_Data\RecommendList.html");

XElement html = XElement.Parse(System.IO.File.ReadAllText(filePath));

Blogs =
(from a in html.Elements("li").Elements("a")
select new BlogInfo { Title = a.Value, Href = a.Attribute("href").Value }).ToList();
}
}

测试时,就把XmlDb.Blogs的内容显示在网页中。
我想这个测试还是比较接近于现实开发的。

这里又有一个问题:我如何测试页面的执行速度?

虽然说创建HttpWebRequest访问页面是个很简单的方法,但我并不打算这样做。
因为从HttpWebRequest发起调用到获取结果,这其中除了有页面的执行时间,还混杂较多的额外调用开销。最终,我选择了在一次HTTP请求中,循环调用Server.Execute来执行页面,并统计时间的方式。其实如何选择测试方法,对于二个测试对象还说,都是公平的。只是说:尽量减少一些额外的调用开销,会让测试结果的差异更大,也更明显。

说明:为了测试代码写起来简单,我使用了MyMVC框架。
测试用例1:WebFromPage.aspx
前面介绍了测试背景以及测试方法。现在就来介绍第1个测试用例,它采用了WebForm编程模型中最经典的写法。
页面代码:
代码如下:


<%@ Page Language="C#" CodeFile="WebFromPage.aspx.cs" Inherits="TestPage_WebFromPage" %>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>PagePerformanceTest http://www.cnblogs.com/fish-li/</title>
</head>
<body>
<p>This is WebFromPage.aspx</p>
<asp:Repeater ID="repeater1" runat="server" onitemdatabound="repeater1_ItemDataBound">
<ItemTemplate>
<asp:HyperLink ID="link1" runat="server"></asp:HyperLink><br />
</ItemTemplate>
<FooterTemplate><hr /></FooterTemplate>
</asp:Repeater>
<asp:Repeater ID="repeater2" runat="server" onitemdatabound="repeater1_ItemDataBound">
<ItemTemplate>
<asp:HyperLink ID="link1" runat="server"></asp:HyperLink><br />
</ItemTemplate>
<FooterTemplate><hr /></FooterTemplate>
</asp:Repeater>
<asp:Repeater ID="repeater3" runat="server" onitemdatabound="repeater1_ItemDataBound">
<ItemTemplate>
<asp:HyperLink ID="link1" runat="server"></asp:HyperLink><br />
</ItemTemplate>
<FooterTemplate><hr /></FooterTemplate>
</asp:Repeater>
<asp:Repeater ID="repeater4" runat="server" onitemdatabound="repeater1_ItemDataBound">
<ItemTemplate>
<asp:HyperLink ID="link1" runat="server"></asp:HyperLink><br />
</ItemTemplate>
<FooterTemplate><hr /></FooterTemplate>
</asp:Repeater>
<asp:Repeater ID="repeater5" runat="server" onitemdatabound="repeater1_ItemDataBound">
<ItemTemplate>
<asp:HyperLink ID="link1" runat="server"></asp:HyperLink><br />
</ItemTemplate>
<FooterTemplate><hr /></FooterTemplate>
</asp:Repeater>

</body>
</html>

页面的CodeFile代码:
代码如下:


public partial class TestPage_WebFromPage : System.Web.UI.Page
{
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
repeater1.DataSource = XmlDb.Blogs;
repeater1.DataBind();
repeater2.DataSource = XmlDb.Blogs;
repeater2.DataBind();
repeater3.DataSource = XmlDb.Blogs;
repeater3.DataBind();
repeater4.DataSource = XmlDb.Blogs;
repeater4.DataBind();
repeater5.DataSource = XmlDb.Blogs;
repeater5.DataBind();
}
protected void repeater1_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
if( e.Item.ItemType == ListItemType.Item ) {
BlogInfo blog = e.Item.DataItem as BlogInfo;
HyperLink link1 = e.Item.FindControl("link1") as HyperLink;
link1.NavigateUrl = blog.Href;
link1.Text = blog.Title;
}
}
}

测试代码:
代码如下:

[Action]
public object Test1(string callTimes)
{
int count = 0;
int.TryParse(callTimes, out count);
if( count <= 0 )
return count;
HttpContext context = HttpContext.Current;
// 先执行一次,排除编译时间
string html = MyMVC.PageExecutor.Render(context, "/TestPage/WebFromPage.aspx", null);
Stopwatch watch = Stopwatch.StartNew();
for( int i = 0; i < count; i++ )
html = MyMVC.PageExecutor.Render(context, "/TestPage/WebFromPage.aspx", null);
watch.Stop();
return watch.Elapsed.ToString();
}

当我测试执行10000次时,耗时:00:00:07.5607229
回到顶部
测试用例2:InlinePage.aspx
与测试用例1不同,测试用例2则完全不使用服务器控件。
页面代码:
代码如下:

<%@ Page Language="C#" %>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>PagePerformanceTest http://www.cnblogs.com/fish-li/</title>
</head>
<body>
<p>This is InlinePage.aspx</p>
<% foreach( BlogInfo b in XmlDb.Blogs ) { %>
<a href="<%= b.Href %>" target="_blank"><%= b.Title %></a><br />
<% } %>
<hr />
<% foreach( BlogInfo b in XmlDb.Blogs ) { %>
<a href="<%= b.Href %>" target="_blank"><%= b.Title %></a><br />
<% } %>
<hr />
<% foreach( BlogInfo b in XmlDb.Blogs ) { %>
<a href="<%= b.Href %>" target="_blank"><%= b.Title %></a><br />
<% } %>
<hr />
<% foreach( BlogInfo b in XmlDb.Blogs ) { %>
<a href="<%= b.Href %>" target="_blank"><%= b.Title %></a><br />
<% } %>
<hr />
<% foreach( BlogInfo b in XmlDb.Blogs ) { %>
<a href="<%= b.Href %>" target="_blank"><%= b.Title %></a><br />
<% } %>
<hr />
</body>
</html>

测试代码:
代码如下:

[Action]
public object Test2(string callTimes)
{
int count = 0;
int.TryParse(callTimes, out count);
if( count <= 0 )
return count;
HttpContext context = HttpContext.Current;
// 先执行一次,排除编译时间
string html = MyMVC.PageExecutor.Render(context, "/TestPage/InlinePage.aspx", null);
Stopwatch watch = Stopwatch.StartNew();
for( int i = 0; i < count; i++ )
html = MyMVC.PageExecutor.Render(context, "/TestPage/InlinePage.aspx", null);
watch.Stop();
return watch.Elapsed.ToString();
}

当我测试执行10000次时,耗时:00:00:01.2345842
回到顶部
分析优化结果1
测试用例1执行相同次数所花费的时间是测试用例2的6倍,为什么会这样呢?
为了回答这个问题,我们首先要知道前面二个页面在执行时,它们是如何运行的。
说到这里,就不得不谈ASP.NET的页面编译方式了。
ASP.NET的页面编译过程是个复杂的操作,其实我们可以不用关心页面是如何编译的,
但要知道:页面编译后是什么样的。
为了能直观地了解页面编译后的样子,我编译了整个网站,并生成到一个DLL文件中,然后使用Reflector.exe来分析这个DLL的源代码。
将网站编译成一个DLL文件有二个方法:
1. 安装WebDeployment插件。
2. 使用我的工具:FishAspnetTool
本文将使用FishAspnetTool来编译测试网站获得编译后的DLL文件。
FishAspnetTool是什么?
FishAspnetTool是我在使用Visual Web Developer 2005时,为了方便编译网站而写的一个小工具。
下载地址://www.gxlcms.com/article/29875.htm
注意:下载的是一个工具包,安装后,从开始菜单中运行FishTools\FishAspnetTool即可。
下面是工具的运行截图。

操作方法:
1. 点击粉色按钮,选择网站路径。
2. 单选按钮选择第2项。
3. 点击【发布网站】按钮。
在编译网站之后,我就可以知道网站在运行时如何运行页面了。
测试用例1的页面,最后被编译成这样了:
代码如下:

namespace ASP
{
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
[CompilerGlobalScope]
public class testpage_webfrompage_aspx : TestPage_WebFromPage, IHttpHandler
{
private static object __fileDependencies;
private static bool __initialized;
[DebuggerNonUserCode]
public testpage_webfrompage_aspx()
{
base.AppRelativeVirtualPath = "~/TestPage/WebFromPage.aspx";
if (!__initialized)
{
string[] virtualFileDependencies = new string[] { "~/TestPage/WebFromPage.aspx", "~/TestPage/WebFromPage.aspx.cs" };
__fileDependencies = base.GetWrappedFileDependencies(virtualFileDependencies);
__initialized = true;
}
base.Server.ScriptTimeout = 0x1c9c380;
}
[DebuggerNonUserCode]
private void __BuildControl__control10(Control __ctrl)
{
IParserAccessor accessor = __ctrl;
accessor.AddParsedSubObject(new LiteralControl("<hr />"));
}
[DebuggerNonUserCode]
private void __BuildControl__control11(Control __ctrl)
{
IParserAccessor accessor = __ctrl;
accessor.AddParsedSubObject(new LiteralControl("\r\n\t"));
HyperLink link = this.__BuildControl__control12();
accessor.AddParsedSubObject(link);
accessor.AddParsedSubObject(new LiteralControl("<br />\r\n"));
}
[DebuggerNonUserCode]
private HyperLink __BuildControl__control12()
{
HyperLink link = new HyperLink {
TemplateControl = this
};
link.ApplyStyleSheetSkin(this);
link.ID = "link1";
return link;
}
[DebuggerNonUserCode]
private void __BuildControl__control13(Control __ctrl)
{
IParserAccessor accessor = __ctrl;
accessor.AddParsedSubObject(new LiteralControl("<hr />"));
}
[DebuggerNonUserCode]
private void __BuildControl__control14(Control __ctrl)
{
IParserAccessor accessor = __ctrl;
accessor.AddParsedSubObject(new LiteralControl("\r\n\t"));
HyperLink link = this.__BuildControl__control15();
accessor.AddParsedSubObject(link);
accessor.AddParsedSubObject(new LiteralControl("<br />\r\n"));
}
[DebuggerNonUserCode]
private HyperLink __BuildControl__control15()
{
HyperLink link = new HyperLink {
TemplateControl = this
};
link.ApplyStyleSheetSkin(this);
link.ID = "link1";
return link;
}
[DebuggerNonUserCode]
private void __BuildControl__control16(Control __ctrl)
{
IParserAccessor accessor = __ctrl;
accessor.AddParsedSubObject(new LiteralControl("<hr />"));
}
[DebuggerNonUserCode]
private void __BuildControl__control2(Control __ctrl)
{
IParserAccessor accessor = __ctrl;
accessor.AddParsedSubObject(new LiteralControl("\r\n\t"));
HyperLink link = this.__BuildControl__control3();
accessor.AddParsedSubObject(link);
accessor.AddParsedSubObject(new LiteralControl("<br />\r\n"));
}
[DebuggerNonUserCode]
private HyperLink __BuildControl__control3()
{
HyperLink link = new HyperLink {
TemplateControl = this
};
link.ApplyStyleSheetSkin(this);
link.ID = "link1";
return link;
}
[DebuggerNonUserCode]
private void __BuildControl__control4(Control __ctrl)
{
IParserAccessor accessor = __ctrl;
accessor.AddParsedSubObject(new LiteralControl("<hr />"));
}
[DebuggerNonUserCode]
private void __BuildControl__control5(Control __ctrl)
{
IParserAccessor accessor = __ctrl;
accessor.AddParsedSubObject(new LiteralControl("\r\n\t"));
HyperLink link = this.__BuildControl__control6();
accessor.AddParsedSubObject(link);
accessor.AddParsedSubObject(new LiteralControl("<br />\r\n"));
}
[DebuggerNonUserCode]
private HyperLink __BuildControl__control6()
{
HyperLink link = new HyperLink {
TemplateControl = this
};
link.ApplyStyleSheetSkin(this);
link.ID = "link1";
return link;
}
[DebuggerNonUserCode]
private void __BuildControl__control7(Control __ctrl)
{
IParserAccessor accessor = __ctrl;
accessor.AddParsedSubObject(new LiteralControl("<hr />"));
}
[DebuggerNonUserCode]
private void __BuildControl__control8(Control __ctrl)
{
IParserAccessor accessor = __ctrl;
accessor.AddParsedSubObject(new LiteralControl("\r\n\t"));
HyperLink link = this.__BuildControl__control9();
accessor.AddParsedSubObject(link);
accessor.AddParsedSubObject(new LiteralControl("<br />\r\n"));
}
[DebuggerNonUserCode]
private HyperLink __BuildControl__control9()
{
HyperLink link = new HyperLink {
TemplateControl = this
};
link.ApplyStyleSheetSkin(this);
link.ID = "link1";
return link;
}
[DebuggerNonUserCode]
private Repeater __BuildControlrepeater1()
{
Repeater repeater = new Repeater();
base.repeater1 = repeater;
repeater.ItemTemplate = new CompiledTemplateBuilder(new BuildTemplateMethod(this.__BuildControl__control2));
repeater.FooterTemplate = new CompiledTemplateBuilder(new BuildTemplateMethod(this.__BuildControl__control4));
repeater.ID = "repeater1";
repeater.ItemDataBound += new RepeaterItemEventHandler(this.repeater1_ItemDataBound);
return repeater;
}
[DebuggerNonUserCode]
private Repeater __BuildControlrepeater2()
{
Repeater repeater = new Repeater();
base.repeater2 = repeater;
repeater.ItemTemplate = new CompiledTemplateBuilder(new BuildTemplateMethod(this.__BuildControl__control5));
repeater.FooterTemplate = new CompiledTemplateBuilder(new BuildTemplateMethod(this.__BuildControl__control7));
repeater.ID = "repeater2";
repeater.ItemDataBound += new RepeaterItemEventHandler(this.repeater1_ItemDataBound);
return repeater;
}
[DebuggerNonUserCode]
private Repeater __BuildControlrepeater3()
{
Repeater repeater = new Repeater();
base.repeater3 = repeater;
repeater.ItemTemplate = new CompiledTemplateBuilder(new BuildTemplateMethod(this.__BuildControl__control8));
repeater.FooterTemplate = new CompiledTemplateBuilder(new BuildTemplateMethod(this.__BuildControl__control10));
repeater.ID = "repeater3";
repeater.ItemDataBound += new RepeaterItemEventHandler(this.repeater1_ItemDataBound);
return repeater;
}
[DebuggerNonUserCode]
private Repeater __BuildControlrepeater4()
{
Repeater repeater = new Repeater();
base.repeater4 = repeater;
repeater.ItemTemplate = new CompiledTemplateBuilder(new BuildTemplateMethod(this.__BuildControl__control11));
repeater.FooterTemplate = new CompiledTemplateBuilder(new BuildTemplateMethod(this.__BuildControl__control13));
repeater.ID = "repeater4";
repeater.ItemDataBound += new RepeaterItemEventHandler(this.repeater1_ItemDataBound);
return repeater;
}
[DebuggerNonUserCode]
private Repeater __BuildControlrepeater5()
{
Repeater repeater = new Repeater();
base.repeater5 = repeater;
repeater.ItemTemplate = new CompiledTemplateBuilder(new BuildTemplateMethod(this.__BuildControl__control14));
repeater.FooterTemplate = new CompiledTemplateBuilder(new BuildTemplateMethod(this.__BuildControl__control16));
repeater.ID = "repeater5";
repeater.ItemDataBound += new RepeaterItemEventHandler(this.repeater1_ItemDataBound);
return repeater;
}
[DebuggerNonUserCode]
private void __BuildControlTree(testpage_webfrompage_aspx __ctrl)
{
__ctrl.EnableViewState = false;
__ctrl.EnableViewStateMac = false;
this.InitializeCulture();
IParserAccessor accessor = __ctrl;
accessor.AddParsedSubObject(new LiteralControl("\r\n\r\n<html xmlns=\"http://www.w3.org/1999/xhtml\">\r\n<head>\r\n <title>PagePerformanceTest http://www.cnblogs.com/fish-li/</title>\r\n</head>\r\n<body>\r\n\r\n<p>This is WebFromPage.aspx</p>\r\n\r\n"));
Repeater repeater = this.__BuildControlrepeater1();
accessor.AddParsedSubObject(repeater);
accessor.AddParsedSubObject(new LiteralControl("\r\n\r\n"));
Repeater repeater2 = this.__BuildControlrepeater2();
accessor.AddParsedSubObject(repeater2);
accessor.AddParsedSubObject(new LiteralControl("\r\n\r\n"));
Repeater repeater3 = this.__BuildControlrepeater3();
accessor.AddParsedSubObject(repeater3);
accessor.AddParsedSubObject(new LiteralControl("\r\n\r\n"));
Repeater repeater4 = this.__BuildControlrepeater4();
accessor.AddParsedSubObject(repeater4);
accessor.AddParsedSubObject(new LiteralControl("\r\n\r\n"));
Repeater repeater5 = this.__BuildControlrepeater5();
accessor.AddParsedSubObject(repeater5);
accessor.AddParsedSubObject(new LiteralControl("\r\n\r\n\r\n</body>\r\n</html>\r\n"));
}
[DebuggerNonUserCode]
protected override void FrameworkInitialize()
{
base.FrameworkInitialize();
this.__BuildControlTree(this);
base.AddWrappedFileDependencies(__fileDependencies);
base.Request.ValidateInput();
}
[DebuggerNonUserCode]
public override int GetTypeHashCode()
{
return -7816338;
}
[DebuggerNonUserCode]
public override void ProcessRequest(HttpContext context)
{
base.ProcessRequest(context);
}
protected override bool SupportAutoEvents
{
get
{
return false;
}
}
}
}

从这个编译结果我们可以看出:页面上的所有文字最后也被包装到LiteralControl中。
页面中呈现时,就是循环调用每个控件的Render方法来最终生成HTML结果。
测试用例2的页面被编译成这个样了:
代码如下:

namespace ASP
{
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Web;
using System.Web.Profile;
using System.Web.UI;
[CompilerGlobalScope]
public class testpage_inlinepage_aspx : Page, IHttpHandler
{
private static object __fileDependencies;
private static bool __initialized;
[DebuggerNonUserCode]
public testpage_inlinepage_aspx()
{
base.AppRelativeVirtualPath = "~/TestPage/InlinePage.aspx";
if (!__initialized)
{
string[] virtualFileDependencies = new string[] { "~/TestPage/InlinePage.aspx" };
__fileDependencies = base.GetWrappedFileDependencies(virtualFileDependencies);
__initialized = true;
}
base.Server.ScriptTimeout = 0x1c9c380;
}
[DebuggerNonUserCode]
private void __BuildControlTree(testpage_inlinepage_aspx __ctrl)
{
__ctrl.EnableViewState = false;
__ctrl.EnableViewStateMac = false;
this.InitializeCulture();
__ctrl.SetRenderMethodDelegate(new RenderMethod(this.__Render__control1));
}
private void __Render__control1(HtmlTextWriter __w, Control parameterContainer)
{
__w.Write("\r\n\r\n<html xmlns=\"http://www.w3.org/1999/xhtml\">\r\n<head>\r\n <title>PagePerformanceTest http://www.cnblogs.com/fish-li/</title>\r\n</head>\r\n<body>\r\n\r\n<p>This is InlinePage.aspx</p>\r\n\r\n");
foreach (BlogInfo info in XmlDb.Blogs)
{
__w.Write("\r\n\t<a href=\"");
__w.Write(info.Href);
__w.Write("\" target=\"_blank\">");
__w.Write(info.Title);
__w.Write("</a><br />\r\n");
}
__w.Write("\r\n<hr />\r\n\r\n");
foreach (BlogInfo info2 in XmlDb.Blogs)
{
__w.Write("\r\n\t<a href=\"");
__w.Write(info2.Href);
__w.Write("\" target=\"_blank\">");
__w.Write(info2.Title);
__w.Write("</a><br />\r\n");
}
__w.Write("\r\n<hr />\r\n\r\n");
foreach (BlogInfo info3 in XmlDb.Blogs)
{
__w.Write("\r\n\t<a href=\"");
__w.Write(info3.Href);
__w.Write("\" target=\"_blank\">");
__w.Write(info3.Title);
__w.Write("</a><br />\r\n");
}
__w.Write("\r\n<hr />\r\n\r\n");
foreach (BlogInfo info4 in XmlDb.Blogs)
{
__w.Write("\r\n\t<a href=\"");
__w.Write(info4.Href);
__w.Write("\" target=\"_blank\">");
__w.Write(info4.Title);
__w.Write("</a><br />\r\n");
}
__w.Write("\r\n<hr />\r\n\r\n");
foreach (BlogInfo info5 in XmlDb.Blogs)
{
__w.Write("\r\n\t<a href=\"");
__w.Write(info5.Href);
__w.Write("\" target=\"_blank\">");
__w.Write(info5.Title);
__w.Write("</a><br />\r\n");
}
__w.Write("\r\n<hr />\r\n\r\n</body>\r\n</html>\r\n");
}
[DebuggerNonUserCode]
protected override void FrameworkInitialize()
{
base.FrameworkInitialize();
this.__BuildControlTree(this);
base.AddWrappedFileDependencies(__fileDependencies);
base.Request.ValidateInput();
}
[DebuggerNonUserCode]
public override int GetTypeHashCode()
{
return -1307842476;
}
[DebuggerNonUserCode]
public override void ProcessRequest(HttpContext context)
{
base.ProcessRequest(context);
}
protected global_asax ApplicationInstance
{
get
{
return (global_asax) this.Context.ApplicationInstance;
}
}
protected DefaultProfile Profile
{
get
{
return (DefaultProfile) this.Context.Profile;
}
}
protected override bool SupportAutoEvents
{
get
{
return false;
}
}
}
}

请注意下面这段关键的代码:它们实在太重要了。
代码如下:

private void __BuildControlTree(testpage_inlinepage_aspx __ctrl)
{
// .......
__ctrl.SetRenderMethodDelegate(new RenderMethod(this.__Render__control1));
}
private void __Render__control1(HtmlTextWriter __w, Control parameterContainer)
{

testpage_inlinepage_aspx与testpage_webfrompage_aspx的编译结果完全不同。
最大的差别在testpage_inlinepage_aspx有个方法:__Render__control1
在这个方法中,页面的内容将直接被写入到HtmlTextWriter对象中。
还有一点我要告诉您:每个Control的
输出最后还是要将自己的显示代码写入到HtmlTextWriter对象中。
因此,从这里就可以明显地看出testpage_inlinepage_aspx的执行速度要快很多,
因为:
1. 它没有服务器控件。
2. 不再需要递归循环每个控件,每个控件的生命周期的调用开销也节省了。
3. 不用再创建那些服务器控件对象,GC的压力会小很多。
4.
输出方式更高效。
通过前面的分析,您现在明白了为什么二个页面的执行速度相差6倍了原因了吧。
好像还有一点没有解释:__Render__control1如何被调用?
我们都知道:以ASPX页面为代表的WebForm编程模型在执行时有一个特点:递归循环每个控件。
页面是在Render阶段
输出的,页面的HTML代码也是在那个阶段
输出到HtmlTextWriter对象中的。
可是,testpage_inlinepage_aspx没有任何控件,那又该如何递归呢?
的确,很多书籍以及技术资料都是说:在Render阶段会递归循环每个控件并调用控件的Render方法。
其实这种说法是不准确的。Control的Render方法在运行时,会调用下面这个方法:
代码如下:

internal void RenderChildrenInternal(HtmlTextWriter writer, ICollection children)
{
if ((this.RareFields != null) && (this.RareFields.RenderMethod != null))
{
writer.BeginRender();
this.RareFields.RenderMethod(writer, this);
writer.EndRender();
}
else if (children != null)
{
foreach (Control control in children)
{
control.RenderControl(writer);
}
}
}

这段代码中,有个重要的if...else...判断,简单说来,就是说要不要调用前面所说的__Render__control1方法。
从代码可以看出,如果是进入了if语句块,则不用递归循环每个控件并调用控件的Render方法。
那么如何能进入if语句块呢?
答案是:调用Control.SetRenderMethodDelegate方法。
testpage_inlinepage_aspx的编译生成代码中就有这个调用。
对于这个方法,MSDN的解释很含糊:
此 API 支持 .NET Framework 基础结构,不适合在代码中直接使用。
分配事件处理程序委托,以将服务器控件及其内容呈现到父控件中。
回到顶部
测试用例3:InlineUserControl.ascx
在测试用例3中,我将页面中用于
输出的代码移到一个用户控件中。
用户控件的代码此处省略,与测试用例2的代码基本上一致。编译后的结果也基本差不多。
测试代码:
代码如下:

[Action]
public object Test3(string callTimes)
{
int count = 0;
int.TryParse(callTimes, out count);
if( count <= 0 )
return count;
// 先执行一次,排除编译时间
string html = MyMVC.UcExecutor.Render("/UserControl/InlineUserControl.ascx", null);
Stopwatch watch = Stopwatch.StartNew();
for( int i = 0; i < count; i++ )
html = MyMVC.UcExecutor.Render("/UserControl/InlineUserControl.ascx", null);
watch.Stop();
return watch.Elapsed.ToString();
}

当我测试执行10000次时,耗时:00:00:00.9132738
又快了一点。
说明:为了这次的性能优化,MyMVC框架也做了一点调整。如果您以前下载过这个框架,请重新下载。
回到顶部
分析优化结果2
经过前面的分析,我们知道:不创建服务器控件对象以及不调用它们的生命周期,可以让页面的执行速度快很多。
有没有再想像一下:页面也有生命周期啊,而且生命周期的步骤更长,省略它,会不会更快呢?
不过,Render方法并不是个public方法,我们还不能直接调用,但可以调用RenderControl方法来实现这一过程。
由于跳过页面的生命周期,任何服务器控件都不能使用了,包括母板页。所以我选择将前面测试用的那段代码移到用户控件中,然后将用户控件加载到Page中来测试。
测试用例3与测试用例2相比,在测试过程中,由于跳过了页面的生命周期,因此速度也就更快了。
嗯,基本上,就是这个简单的原因吧。
由于这种方法没有任何的控件生命周期,因此速度是最快的。
经过这一系列的优化,页面的执行时间最终由 00:00:07.5607229 减少到 00:00:00.9132738

点击此处下载示例代码

下载本文
显示全文
专题