ASP.NET页面优化:性能提升8倍的方法

开发 后端
本文的测试结果也仅仅只是一个参考数字,这个结果也只是根据我所设计的测试页面得出的。优化的过程中,如果不使用服务器控件,那么给GC减少的压力其实也是无法测试到的。在测试过程中,我还发现测试结果并不是很稳定,因此截图具有一定的偶然性。测试页面或许在某些方面存在一些片面性,因此,结果仅供参考。

阅读目录


  1. 开始
  2. 测试背景
  3. 测试方法
  4. 测试用例1:WebFromPage.aspx
  5. 测试用例2:InlinePage.aspx
  6. 分析优化结果1
  7. 测试用例3:InlineUserControl.ascx
  8. 分析优化结果2

今天与大家分享:一种优化页面执行速度的方法。

采用这个方法,可以使用页面的执行速度获得【8倍】的提升效果。

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

测试环境:

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

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

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

本文的测试结果也仅仅只是一个参考数字,这个结果也只是根据我所设计的测试页面得出的。

优化的过程中,如果不使用服务器控件,那么给GC减少的压力其实也是无法测试到的。

在测试过程中,我还发现测试结果并不是很稳定,因此截图具有一定的偶然性。

测试页面或许在某些方面存在一些片面性,因此,结果仅供参考。

[[61536]]

测试背景

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

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

真是这样吗?

今天测试的起点就从使用简单的服务器开始,我会分二次对它做一系列的性能优化。

最终就是上图中的3个结果,它们反映了二次优化的改进过程。

 

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

优化的过程涉及到ASP.NET服务器控件的使用,测试结果也仅仅只是一个参考数字。

如果您认为您的开发工作非常依赖于服务器控件的使用,

那么测试结果对您来说其实是无意义的,请不要在意这个结果。

[[61537]]

测试方法

在这次优化过程中,我并没有设计很复杂的测试页面,而是一个很简单的测试页面,页面显示效果如下:

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

测试的数据是这样获取的:

我复制了我的博客侧边栏的【推荐排行榜】的那段HTML代码,保存到一个文件中:

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

  1. public class BlogInfo  
  2. {  
  3.     public string Title;  
  4.     public string Href;  
  5. }  
  6.  
  7. public static class XmlDb  
  8. {  
  9.     public static List<BlogInfo> Blogs { getprivate set; }  
  10.  
  11.  
  12.     public static void LoadBlogs()  
  13.     {  
  14.         string filePath = Path.Combine(HttpRuntime.AppDomainAppPath, @"App_Data\RecommendList.html");  
  15.  
  16.         XElement html = XElement.Parse(System.IO.File.ReadAllText(filePath));  
  17.  
  18.         Blogs =  
  19.             (from a in html.Elements("li").Elements("a")  
  20.              select new BlogInfo { Title = a.Value, Href = a.Attribute("href").Value }).ToList();  
  21.     }  

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

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

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

说明:为了测试代码写起来简单,我使用了MyMVC框架。

[[61538]]

测试用例1:WebFromPage.aspx

前面介绍了测试背景以及测试方法。现在就来介绍第1个测试用例,它采用了WebForm编程模型中最经典的写法。

页面代码:

  1. <%@ Page Language="C#" CodeFile="WebFromPage.aspx.cs" Inherits="TestPage_WebFromPage" %> 
  2.  
  3. <html xmlns="http://www.w3.org/1999/xhtml"> 
  4. <head> 
  5.     <title>PagePerformanceTest   http://www.cnblogs.com/fish-li/</title> 
  6. </head> 
  7. <body> 
  8.  
  9. <p>This is WebFromPage.aspx</p> 
  10.  
  11. <asp:Repeater ID="repeater1" runat="server" onitemdatabound="repeater1_ItemDataBound"> 
  12. <ItemTemplate> 
  13.     <asp:HyperLink ID="link1" runat="server"></asp:HyperLink><br /> 
  14. </ItemTemplate> 
  15. <FooterTemplate><hr /></FooterTemplate> 
  16. </asp:Repeater> 
  17.  
  18. <asp:Repeater ID="repeater2" runat="server" onitemdatabound="repeater1_ItemDataBound"> 
  19. <ItemTemplate> 
  20.     <asp:HyperLink ID="link1" runat="server"></asp:HyperLink><br /> 
  21. </ItemTemplate> 
  22. <FooterTemplate><hr /></FooterTemplate> 
  23. </asp:Repeater> 
  24.  
  25. <asp:Repeater ID="repeater3" runat="server" onitemdatabound="repeater1_ItemDataBound"> 
  26. <ItemTemplate> 
  27.     <asp:HyperLink ID="link1" runat="server"></asp:HyperLink><br /> 
  28. </ItemTemplate> 
  29. <FooterTemplate><hr /></FooterTemplate> 
  30. </asp:Repeater> 
  31.  
  32. <asp:Repeater ID="repeater4" runat="server" onitemdatabound="repeater1_ItemDataBound"> 
  33. <ItemTemplate> 
  34.     <asp:HyperLink ID="link1" runat="server"></asp:HyperLink><br /> 
  35. </ItemTemplate> 
  36. <FooterTemplate><hr /></FooterTemplate> 
  37. </asp:Repeater> 
  38.  
  39. <asp:Repeater ID="repeater5" runat="server" onitemdatabound="repeater1_ItemDataBound"> 
  40. <ItemTemplate> 
  41.     <asp:HyperLink ID="link1" runat="server"></asp:HyperLink><br /> 
  42. </ItemTemplate> 
  43. <FooterTemplate><hr /></FooterTemplate> 
  44. </asp:Repeater> 
  45.  
  46.  
  47. </body> 
  48. </html> 

页面的CodeFile代码:

  1. public partial class TestPage_WebFromPage : System.Web.UI.Page  
  2. {  
  3.     protected override void OnLoad(EventArgs e)  
  4.     {  
  5.         base.OnLoad(e);  
  6.  
  7.         repeater1.DataSource = XmlDb.Blogs;  
  8.         repeater1.DataBind();  
  9.         repeater2.DataSource = XmlDb.Blogs;  
  10.         repeater2.DataBind();  
  11.         repeater3.DataSource = XmlDb.Blogs;  
  12.         repeater3.DataBind();  
  13.         repeater4.DataSource = XmlDb.Blogs;  
  14.         repeater4.DataBind();  
  15.         repeater5.DataSource = XmlDb.Blogs;  
  16.         repeater5.DataBind();  
  17.     }  
  18.  
  19.     protected void repeater1_ItemDataBound(object sender, RepeaterItemEventArgs e)  
  20.     {  
  21.         if( e.Item.ItemType == ListItemType.Item ) {  
  22.             BlogInfo blog = e.Item.DataItem as BlogInfo;  
  23.             HyperLink link1 = e.Item.FindControl("link1"as HyperLink;  
  24.             link1.NavigateUrl = blog.Href;  
  25.             link1.Text = blog.Title;  
  26.         }  
  27.     }  

测试代码:

  1. [Action]  
  2. public object Test1(string callTimes)  
  3. {  
  4.     int count = 0;  
  5.     int.TryParse(callTimes, out count);  
  6.     if( count <= 0 )  
  7.         return count;  
  8.  
  9.     HttpContext context = HttpContext.Current;  
  10.       
  11.     // 先执行一次,排除编译时间  
  12.     string html = MyMVC.PageExecutor.Render(context, "/TestPage/WebFromPage.aspx"null);  
  13.  
  14.     Stopwatch watch = Stopwatch.StartNew();  
  15.     forint i = 0; i < count; i++ )  
  16.         html = MyMVC.PageExecutor.Render(context, "/TestPage/WebFromPage.aspx"null);  
  17.     watch.Stop();  
  18.  
  19.     return watch.Elapsed.ToString();  

当我测试执行10000次时,耗时:00:00:07.5607229

[[61539]]

测试用例2:InlinePage.aspx

与测试用例1不同,测试用例2则完全不使用服务器控件。

页面代码:

  1. <%@ Page Language="C#" %> 
  2.  
  3. <html xmlns="http://www.w3.org/1999/xhtml"> 
  4. <head> 
  5.     <title>PagePerformanceTest   http://www.cnblogs.com/fish-li/</title> 
  6. </head> 
  7. <body> 
  8.  
  9. <p>This is InlinePage.aspx</p> 
  10.  
  11. <% foreach( BlogInfo b in XmlDb.Blogs ) { %> 
  12.     <a href="<%= b.Href %>" target="_blank"><%= b.Title %></a><br /> 
  13. <% } %> 
  14. <hr /> 
  15.  
  16. <% foreach( BlogInfo b in XmlDb.Blogs ) { %> 
  17.     <a href="<%= b.Href %>" target="_blank"><%= b.Title %></a><br /> 
  18. <% } %> 
  19. <hr /> 
  20.  
  21. <% foreach( BlogInfo b in XmlDb.Blogs ) { %> 
  22.     <a href="<%= b.Href %>" target="_blank"><%= b.Title %></a><br /> 
  23. <% } %> 
  24. <hr /> 
  25.  
  26. <% foreach( BlogInfo b in XmlDb.Blogs ) { %> 
  27.     <a href="<%= b.Href %>" target="_blank"><%= b.Title %></a><br /> 
  28. <% } %> 
  29. <hr /> 
  30.  
  31. <% foreach( BlogInfo b in XmlDb.Blogs ) { %> 
  32.     <a href="<%= b.Href %>" target="_blank"><%= b.Title %></a><br /> 
  33. <% } %> 
  34. <hr /> 
  35.  
  36. </body> 
  37. </html> 

测试代码:

  1. [Action]  
  2. public object Test2(string callTimes)  
  3. {  
  4.     int count = 0;  
  5.     int.TryParse(callTimes, out count);  
  6.     if( count <= 0 )  
  7.         return count;  
  8.  
  9.     HttpContext context = HttpContext.Current;  
  10.  
  11.     // 先执行一次,排除编译时间  
  12.     string html = MyMVC.PageExecutor.Render(context, "/TestPage/InlinePage.aspx"null);  
  13.  
  14.     Stopwatch watch = Stopwatch.StartNew();  
  15.     forint i = 0; i < count; i++ )  
  16.         html = MyMVC.PageExecutor.Render(context, "/TestPage/InlinePage.aspx"null);  
  17.     watch.Stop();  
  18.  
  19.     return watch.Elapsed.ToString();  

当我测试执行10000次时,耗时:00:00:01.2345842

[[61540]]

分析优化结果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时,为了方便编译网站而写的一个小工具。

下载地址:http://www.cnblogs.com/fish-li/archive/2011/10/30/2229497.html

注意:下载的是一个工具包,安装后,从开始菜单中运行FishTools\FishAspnetTool即可。

下面是工具的运行截图。

操作方法:

1. 点击粉色按钮,选择网站路径。

2. 单选按钮选择第2项。

3. 点击【发布网站】按钮。

在编译网站之后,我就可以知道网站在运行时如何运行页面了。

测试用例1的页面,最后被编译成这样了:

  1. namespace ASP  
  2. {  
  3.     using System;  
  4.     using System.Diagnostics;  
  5.     using System.Runtime.CompilerServices;  
  6.     using System.Web;  
  7.     using System.Web.UI;  
  8.     using System.Web.UI.WebControls;  
  9.  
  10.     [CompilerGlobalScope]  
  11.     public class testpage_webfrompage_aspx : TestPage_WebFromPage, IHttpHandler  
  12.     {  
  13.         private static object __fileDependencies;  
  14.         private static bool __initialized;  
  15.  
  16.         [DebuggerNonUserCode]  
  17.         public testpage_webfrompage_aspx()  
  18.         {  
  19.             base.AppRelativeVirtualPath = "~/TestPage/WebFromPage.aspx";  
  20.             if (!__initialized)  
  21.             {  
  22.                 string[] virtualFileDependencies = new string[] { "~/TestPage/WebFromPage.aspx""~/TestPage/WebFromPage.aspx.cs" };  
  23.                 __fileDependencies = base.GetWrappedFileDependencies(virtualFileDependencies);  
  24.                 __initialized = true;  
  25.             }  
  26.             base.Server.ScriptTimeout = 0x1c9c380;  
  27.         }  
  28.  
  29.         [DebuggerNonUserCode]  
  30.         private void __BuildControl__control10(Control __ctrl)  
  31.         {  
  32.             IParserAccessor accessor = __ctrl;  
  33.             accessor.AddParsedSubObject(new LiteralControl("<hr />"));  
  34.         }  
  35.  
  36.         [DebuggerNonUserCode]  
  37.         private void __BuildControl__control11(Control __ctrl)  
  38.         {  
  39.             IParserAccessor accessor = __ctrl;  
  40.             accessor.AddParsedSubObject(new LiteralControl("\r\n\t"));  
  41.             HyperLink link = this.__BuildControl__control12();  
  42.             accessor.AddParsedSubObject(link);  
  43.             accessor.AddParsedSubObject(new LiteralControl("<br />\r\n"));  
  44.         }  
  45.  
  46.         [DebuggerNonUserCode]  
  47.         private HyperLink __BuildControl__control12()  
  48.         {  
  49.             HyperLink link = new HyperLink {  
  50.                 TemplateControl = this 
  51.             };  
  52.             link.ApplyStyleSheetSkin(this);  
  53.             link.ID = "link1";  
  54.             return link;  
  55.         }  
  56.  
  57.         [DebuggerNonUserCode]  
  58.         private void __BuildControl__control13(Control __ctrl)  
  59.         {  
  60.             IParserAccessor accessor = __ctrl;  
  61.             accessor.AddParsedSubObject(new LiteralControl("<hr />"));  
  62.         }  
  63.  
  64.         [DebuggerNonUserCode]  
  65.         private void __BuildControl__control14(Control __ctrl)  
  66.         {  
  67.             IParserAccessor accessor = __ctrl;  
  68.             accessor.AddParsedSubObject(new LiteralControl("\r\n\t"));  
  69.             HyperLink link = this.__BuildControl__control15();  
  70.             accessor.AddParsedSubObject(link);  
  71.             accessor.AddParsedSubObject(new LiteralControl("<br />\r\n"));  
  72.         }  
  73.  
  74.         [DebuggerNonUserCode]  
  75.         private HyperLink __BuildControl__control15()  
  76.         {  
  77.             HyperLink link = new HyperLink {  
  78.                 TemplateControl = this 
  79.             };  
  80.             link.ApplyStyleSheetSkin(this);  
  81.             link.ID = "link1";  
  82.             return link;  
  83.         }  
  84.  
  85.         [DebuggerNonUserCode]  
  86.         private void __BuildControl__control16(Control __ctrl)  
  87.         {  
  88.             IParserAccessor accessor = __ctrl;  
  89.             accessor.AddParsedSubObject(new LiteralControl("<hr />"));  
  90.         }  
  91.  
  92.         [DebuggerNonUserCode]  
  93.         private void __BuildControl__control2(Control __ctrl)  
  94.         {  
  95.             IParserAccessor accessor = __ctrl;  
  96.             accessor.AddParsedSubObject(new LiteralControl("\r\n\t"));  
  97.             HyperLink link = this.__BuildControl__control3();  
  98.             accessor.AddParsedSubObject(link);  
  99.             accessor.AddParsedSubObject(new LiteralControl("<br />\r\n"));  
  100.         }  
  101.  
  102.         [DebuggerNonUserCode]  
  103.         private HyperLink __BuildControl__control3()  
  104.         {  
  105.             HyperLink link = new HyperLink {  
  106.                 TemplateControl = this 
  107.             };  
  108.             link.ApplyStyleSheetSkin(this);  
  109.             link.ID = "link1";  
  110.             return link;  
  111.         }  
  112.  
  113.         [DebuggerNonUserCode]  
  114.         private void __BuildControl__control4(Control __ctrl)  
  115.         {  
  116.             IParserAccessor accessor = __ctrl;  
  117.             accessor.AddParsedSubObject(new LiteralControl("<hr />"));  
  118.         }  
  119.  
  120.         [DebuggerNonUserCode]  
  121.         private void __BuildControl__control5(Control __ctrl)  
  122.         {  
  123.             IParserAccessor accessor = __ctrl;  
  124.             accessor.AddParsedSubObject(new LiteralControl("\r\n\t"));  
  125.             HyperLink link = this.__BuildControl__control6();  
  126.             accessor.AddParsedSubObject(link);  
  127.             accessor.AddParsedSubObject(new LiteralControl("<br />\r\n"));  
  128.         }  
  129.  
  130.         [DebuggerNonUserCode]  
  131.         private HyperLink __BuildControl__control6()  
  132.         {  
  133.             HyperLink link = new HyperLink {  
  134.                 TemplateControl = this 
  135.             };  
  136.             link.ApplyStyleSheetSkin(this);  
  137.             link.ID = "link1";  
  138.             return link;  
  139.         }  
  140.  
  141.         [DebuggerNonUserCode]  
  142.         private void __BuildControl__control7(Control __ctrl)  
  143.         {  
  144.             IParserAccessor accessor = __ctrl;  
  145.             accessor.AddParsedSubObject(new LiteralControl("<hr />"));  
  146.         }  
  147.  
  148.         [DebuggerNonUserCode]  
  149.         private void __BuildControl__control8(Control __ctrl)  
  150.         {  
  151.             IParserAccessor accessor = __ctrl;  
  152.             accessor.AddParsedSubObject(new LiteralControl("\r\n\t"));  
  153.             HyperLink link = this.__BuildControl__control9();  
  154.             accessor.AddParsedSubObject(link);  
  155.             accessor.AddParsedSubObject(new LiteralControl("<br />\r\n"));  
  156.         }  
  157.  
  158.         [DebuggerNonUserCode]  
  159.         private HyperLink __BuildControl__control9()  
  160.         {  
  161.             HyperLink link = new HyperLink {  
  162.                 TemplateControl = this 
  163.             };  
  164.             link.ApplyStyleSheetSkin(this);  
  165.             link.ID = "link1";  
  166.             return link;  
  167.         }  
  168.  
  169.         [DebuggerNonUserCode]  
  170.         private Repeater __BuildControlrepeater1()  
  171.         {  
  172.             Repeater repeater = new Repeater();  
  173.             base.repeater1 = repeater;  
  174.             repeater.ItemTemplate = new CompiledTemplateBuilder(new BuildTemplateMethod(this.__BuildControl__control2));  
  175.             repeater.FooterTemplate = new CompiledTemplateBuilder(new BuildTemplateMethod(this.__BuildControl__control4));  
  176.             repeater.ID = "repeater1";  
  177.             repeater.ItemDataBound += new RepeaterItemEventHandler(this.repeater1_ItemDataBound);  
  178.             return repeater;  
  179.         }  
  180.  
  181.         [DebuggerNonUserCode]  
  182.         private Repeater __BuildControlrepeater2()  
  183.         {  
  184.             Repeater repeater = new Repeater();  
  185.             base.repeater2 = repeater;  
  186.             repeater.ItemTemplate = new CompiledTemplateBuilder(new BuildTemplateMethod(this.__BuildControl__control5));  
  187.             repeater.FooterTemplate = new CompiledTemplateBuilder(new BuildTemplateMethod(this.__BuildControl__control7));  
  188.             repeater.ID = "repeater2";  
  189.             repeater.ItemDataBound += new RepeaterItemEventHandler(this.repeater1_ItemDataBound);  
  190.             return repeater;  
  191.         }  
  192.  
  193.         [DebuggerNonUserCode]  
  194.         private Repeater __BuildControlrepeater3()  
  195.         {  
  196.             Repeater repeater = new Repeater();  
  197.             base.repeater3 = repeater;  
  198.             repeater.ItemTemplate = new CompiledTemplateBuilder(new BuildTemplateMethod(this.__BuildControl__control8));  
  199.             repeater.FooterTemplate = new CompiledTemplateBuilder(new BuildTemplateMethod(this.__BuildControl__control10));  
  200.             repeater.ID = "repeater3";  
  201.             repeater.ItemDataBound += new RepeaterItemEventHandler(this.repeater1_ItemDataBound);  
  202.             return repeater;  
  203.         }  
  204.  
  205.         [DebuggerNonUserCode]  
  206.         private Repeater __BuildControlrepeater4()  
  207.         {  
  208.             Repeater repeater = new Repeater();  
  209.             base.repeater4 = repeater;  
  210.             repeater.ItemTemplate = new CompiledTemplateBuilder(new BuildTemplateMethod(this.__BuildControl__control11));  
  211.             repeater.FooterTemplate = new CompiledTemplateBuilder(new BuildTemplateMethod(this.__BuildControl__control13));  
  212.             repeater.ID = "repeater4";  
  213.             repeater.ItemDataBound += new RepeaterItemEventHandler(this.repeater1_ItemDataBound);  
  214.             return repeater;  
  215.         }  
  216.  
  217.         [DebuggerNonUserCode]  
  218.         private Repeater __BuildControlrepeater5()  
  219.         {  
  220.             Repeater repeater = new Repeater();  
  221.             base.repeater5 = repeater;  
  222.             repeater.ItemTemplate = new CompiledTemplateBuilder(new BuildTemplateMethod(this.__BuildControl__control14));  
  223.             repeater.FooterTemplate = new CompiledTemplateBuilder(new BuildTemplateMethod(this.__BuildControl__control16));  
  224.             repeater.ID = "repeater5";  
  225.             repeater.ItemDataBound += new RepeaterItemEventHandler(this.repeater1_ItemDataBound);  
  226.             return repeater;  
  227.         }  
  228.  
  229.         [DebuggerNonUserCode]  
  230.         private void __BuildControlTree(testpage_webfrompage_aspx __ctrl)  
  231.         {  
  232.             __ctrl.EnableViewState = false;  
  233.             __ctrl.EnableViewStateMac = false;  
  234.             this.InitializeCulture();  
  235.             IParserAccessor accessor = __ctrl;  
  236.             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"));  
  237.             Repeater repeater = this.__BuildControlrepeater1();  
  238.             accessor.AddParsedSubObject(repeater);  
  239.             accessor.AddParsedSubObject(new LiteralControl("\r\n\r\n"));  
  240.             Repeater repeater2 = this.__BuildControlrepeater2();  
  241.             accessor.AddParsedSubObject(repeater2);  
  242.             accessor.AddParsedSubObject(new LiteralControl("\r\n\r\n"));  
  243.             Repeater repeater3 = this.__BuildControlrepeater3();  
  244.             accessor.AddParsedSubObject(repeater3);  
  245.             accessor.AddParsedSubObject(new LiteralControl("\r\n\r\n"));  
  246.             Repeater repeater4 = this.__BuildControlrepeater4();  
  247.             accessor.AddParsedSubObject(repeater4);  
  248.             accessor.AddParsedSubObject(new LiteralControl("\r\n\r\n"));  
  249.             Repeater repeater5 = this.__BuildControlrepeater5();  
  250.             accessor.AddParsedSubObject(repeater5);  
  251.             accessor.AddParsedSubObject(new LiteralControl("\r\n\r\n\r\n</body>\r\n</html>\r\n"));  
  252.         }  
  253.  
  254.         [DebuggerNonUserCode]  
  255.         protected override void FrameworkInitialize()  
  256.         {  
  257.             base.FrameworkInitialize();  
  258.             this.__BuildControlTree(this);  
  259.             base.AddWrappedFileDependencies(__fileDependencies);  
  260.             base.Request.ValidateInput();  
  261.         }  
  262.  
  263.         [DebuggerNonUserCode]  
  264.         public override int GetTypeHashCode()  
  265.         {  
  266.             return -781896338;  
  267.         }  
  268.  
  269.         [DebuggerNonUserCode]  
  270.         public override void ProcessRequest(HttpContext context)  
  271.         {  
  272.             base.ProcessRequest(context);  
  273.         }  
  274.  
  275.         protected override bool SupportAutoEvents  
  276.         {  
  277.             get  
  278.             {  
  279.                 return false;  
  280.             }  
  281.         }  
  282.     }  

从这个编译结果我们可以看出:页面上的所有文字最后也被包装到LiteralControl中。

页面中呈现时,就是循环调用每个控件的Render方法来最终生成HTML结果。

测试用例2的页面被编译成这个样了:

  1. namespace ASP  
  2. {  
  3.     using System;  
  4.     using System.Diagnostics;  
  5.     using System.Runtime.CompilerServices;  
  6.     using System.Web;  
  7.     using System.Web.Profile;  
  8.     using System.Web.UI;  
  9.  
  10.     [CompilerGlobalScope]  
  11.     public class testpage_inlinepage_aspx : Page, IHttpHandler  
  12.     {  
  13.         private static object __fileDependencies;  
  14.         private static bool __initialized;  
  15.  
  16.         [DebuggerNonUserCode]  
  17.         public testpage_inlinepage_aspx()  
  18.         {  
  19.             base.AppRelativeVirtualPath = "~/TestPage/InlinePage.aspx";  
  20.             if (!__initialized)  
  21.             {  
  22.                 string[] virtualFileDependencies = new string[] { "~/TestPage/InlinePage.aspx" };  
  23.                 __fileDependencies = base.GetWrappedFileDependencies(virtualFileDependencies);  
  24.                 __initialized = true;  
  25.             }  
  26.             base.Server.ScriptTimeout = 0x1c9c380;  
  27.         }  
  28.  
  29.         [DebuggerNonUserCode]  
  30.         private void __BuildControlTree(testpage_inlinepage_aspx __ctrl)  
  31.         {  
  32.             __ctrl.EnableViewState = false;  
  33.             __ctrl.EnableViewStateMac = false;  
  34.             this.InitializeCulture();  
  35.             __ctrl.SetRenderMethodDelegate(new RenderMethod(this.__Render__control1));  
  36.         }  
  37.  
  38.         private void __Render__control1(HtmlTextWriter __w, Control parameterContainer)  
  39.         {  
  40.             __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");  
  41.             foreach (BlogInfo info in XmlDb.Blogs)  
  42.             {  
  43.                 __w.Write("\r\n\t<a href=\"");  
  44.                 __w.Write(info.Href);  
  45.                 __w.Write("\" target=\"_blank\">");  
  46.                 __w.Write(info.Title);  
  47.                 __w.Write("</a><br />\r\n");  
  48.             }  
  49.             __w.Write("\r\n<hr />\r\n\r\n");  
  50.             foreach (BlogInfo info2 in XmlDb.Blogs)  
  51.             {  
  52.                 __w.Write("\r\n\t<a href=\"");  
  53.                 __w.Write(info2.Href);  
  54.                 __w.Write("\" target=\"_blank\">");  
  55.                 __w.Write(info2.Title);  
  56.                 __w.Write("</a><br />\r\n");  
  57.             }  
  58.             __w.Write("\r\n<hr />\r\n\r\n");  
  59.             foreach (BlogInfo info3 in XmlDb.Blogs)  
  60.             {  
  61.                 __w.Write("\r\n\t<a href=\"");  
  62.                 __w.Write(info3.Href);  
  63.                 __w.Write("\" target=\"_blank\">");  
  64.                 __w.Write(info3.Title);  
  65.                 __w.Write("</a><br />\r\n");  
  66.             }  
  67.             __w.Write("\r\n<hr />\r\n\r\n");  
  68.             foreach (BlogInfo info4 in XmlDb.Blogs)  
  69.             {  
  70.                 __w.Write("\r\n\t<a href=\"");  
  71.                 __w.Write(info4.Href);  
  72.                 __w.Write("\" target=\"_blank\">");  
  73.                 __w.Write(info4.Title);  
  74.                 __w.Write("</a><br />\r\n");  
  75.             }  
  76.             __w.Write("\r\n<hr />\r\n\r\n");  
  77.             foreach (BlogInfo info5 in XmlDb.Blogs)  
  78.             {  
  79.                 __w.Write("\r\n\t<a href=\"");  
  80.                 __w.Write(info5.Href);  
  81.                 __w.Write("\" target=\"_blank\">");  
  82.                 __w.Write(info5.Title);  
  83.                 __w.Write("</a><br />\r\n");  
  84.             }  
  85.             __w.Write("\r\n<hr />\r\n\r\n</body>\r\n</html>\r\n");  
  86.         }  
  87.  
  88.         [DebuggerNonUserCode]  
  89.         protected override void FrameworkInitialize()  
  90.         {  
  91.             base.FrameworkInitialize();  
  92.             this.__BuildControlTree(this);  
  93.             base.AddWrappedFileDependencies(__fileDependencies);  
  94.             base.Request.ValidateInput();  
  95.         }  
  96.  
  97.         [DebuggerNonUserCode]  
  98.         public override int GetTypeHashCode()  
  99.         {  
  100.             return -1307842476;  
  101.         }  
  102.  
  103.         [DebuggerNonUserCode]  
  104.         public override void ProcessRequest(HttpContext context)  
  105.         {  
  106.             base.ProcessRequest(context);  
  107.         }  
  108.  
  109.         protected global_asax ApplicationInstance  
  110.         {  
  111.             get  
  112.             {  
  113.                 return (global_asax) this.Context.ApplicationInstance;  
  114.             }  
  115.         }  
  116.  
  117.         protected DefaultProfile Profile  
  118.         {  
  119.             get  
  120.             {  
  121.                 return (DefaultProfile) this.Context.Profile;  
  122.             }  
  123.         }  
  124.  
  125.         protected override bool SupportAutoEvents  
  126.         {  
  127.             get  
  128.             {  
  129.                 return false;  
  130.             }  
  131.         }  
  132.     }  

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

  1. private void __BuildControlTree(testpage_inlinepage_aspx __ctrl)  
  2. {  
  3.    // .......   
  4.     __ctrl.SetRenderMethodDelegate(new RenderMethod(this.__Render__control1));  
  5. }  
  6.  
  7. 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方法在运行时,会调用下面这个方法:

  1. internal void RenderChildrenInternal(HtmlTextWriter writer, ICollection children)  
  2. {  
  3.     if ((this.RareFields != null) && (this.RareFields.RenderMethod != null))  
  4.     {  
  5.         writer.BeginRender();  
  6.         this.RareFields.RenderMethod(writer, this);  
  7.         writer.EndRender();  
  8.     }  
  9.     else if (children != null)  
  10.     {  
  11.         foreach (Control control in children)  
  12.         {  
  13.             control.RenderControl(writer);  
  14.         }  
  15.     }  

这段代码中,有个重要的if...else...判断,简单说来,就是说要不要调用前面所说的__Render__control1方法。

从代码可以看出,如果是进入了if语句块,则不用递归循环每个控件并调用控件的Render方法。

那么如何能进入if语句块呢?

答案是:调用Control.SetRenderMethodDelegate方法。

testpage_inlinepage_aspx的编译生成代码中就有这个调用。

对于这个方法,MSDN的解释很含糊:

此 API 支持 .NET Framework 基础结构,不适合在代码中直接使用。

分配事件处理程序委托,以将服务器控件及其内容呈现到父控件中。

[[61541]]

测试用例3:InlineUserControl.ascx

在测试用例3中,我将页面中用于输出的代码移到一个用户控件中。

用户控件的代码此处省略,与测试用例2的代码基本上一致。编译后的结果也基本差不多。

测试代码:

  1. [Action]  
  2. public object Test3(string callTimes)  
  3. {  
  4.     int count = 0;  
  5.     int.TryParse(callTimes, out count);  
  6.     if( count <= 0 )  
  7.         return count;  
  8.  
  9.     // 先执行一次,排除编译时间  
  10.     string html = MyMVC.UcExecutor.Render("/UserControl/InlineUserControl.ascx"null);  
  11.       
  12.     Stopwatch watch = Stopwatch.StartNew();  
  13.     forint i = 0; i < count; i++ )  
  14.         html = MyMVC.UcExecutor.Render("/UserControl/InlineUserControl.ascx"null);  
  15.     watch.Stop();  
  16.  
  17.     return watch.Elapsed.ToString();  

当我测试执行10000次时,耗时:00:00:00.9132738

又快了一点。

说明:为了这次的性能优化,MyMVC框架也做了一点调整。如果您以前下载过这个框架,请重新下载。

[[61542]]

分析优化结果2

经过前面的分析,我们知道:不创建服务器控件对象以及不调用它们的生命周期,可以让页面的执行速度快很多。

有没有再想像一下:页面也有生命周期啊,而且生命周期的步骤更长,省略它,会不会更快呢?

不过,Render方法并不是个public方法,我们还不能直接调用,但可以调用RenderControl方法来实现这一过程。

由于跳过页面的生命周期,任何服务器控件都不能使用了,包括母板页。所以我选择将前面测试用的那段代码移到用户控件中,然后将用户控件加载到Page中来测试。

测试用例3与测试用例2相比,在测试过程中,由于跳过了页面的生命周期,因此速度也就更快了。

注意:事实上,动态加载用户控件也会有一定的调用开销。这种方法也仅供参考,可以根据实际情况来选择。

嗯,基本上,就是这个简单的原因吧。

由于这种方法没有任何的控件生命周期,因此速度是最快的。

经过这一系列的优化,页面的执行时间最终由 00:00:07.5607229 减少到 00:00:00.9132738

再次申明:测试结果也仅仅只是一个参考数字。

事实上,使用服务器控件产生的对象涉及到GC的回收以及内存占用的影响也是不可忽视的。

原文链接:http://www.cnblogs.com/fish-li/archive/2012/03/11/2390569.html

【编辑推荐】

  1. 如何在ASP.NET网站中使用HTML 5拖放功能
  2. 通过标准的ASP.NET控件来使用jQuery UI
  3. 另类的ASP.NET快速开发架构体系
  4. 从ASP.NET MVC 2到4看异步控制器
  5. ASP.NET MVC基于异常处理的解决方案

 

责任编辑:林师授 来源: Fish Li的博客
相关推荐

2014-07-31 09:28:09

ASP.NETWeb API

2009-02-23 15:55:29

ASP.NET.NET性能提升

2012-12-24 09:23:27

ASP.NETC#IIS

2009-03-25 09:16:23

数据库优化ASP.NET

2011-06-28 15:14:10

ASP.NET性能优化

2018-02-23 13:55:16

ASP.NET性能优化技巧

2011-10-19 09:41:15

ASP.NET性能优化

2009-08-13 15:49:18

ASP.NET性能优化

2012-05-16 10:24:26

ASP.NET性能优化

2009-08-13 16:22:18

ASP.NET性能优化

2009-07-24 10:53:51

ASP.NET实现静态

2009-07-31 10:23:44

缓存页面ASP.NET缓存

2009-07-23 14:17:41

2009-07-29 17:26:39

ASP.NET页面

2009-07-20 17:07:30

提高ASP.NET性能

2011-10-17 09:54:18

ASP.NET性能

2011-07-06 08:46:30

2021-05-19 08:04:11

ASP.Net服务性原则

2009-12-10 09:23:13

ASP.NET开发

2009-07-31 08:56:59

ASP.NET页面刷新
点赞
收藏

51CTO技术栈公众号