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

作者:   出处:互联网   2015-07-08 21:00:12   阅读:1

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

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


 

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


 

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


 


 

测试环境:


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

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


 

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


 

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


 

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


 

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


 

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


 


 

测试背景


 

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


 

现在有很多做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文件有二个方法:


安装WebDeployment插件。
使用工具:FishAspnetTool
 

本文将使用FishAspnetTool来编译测试网站获得编译后的DLL文件。


 

FishAspnetTool是什么?


 

FishAspnetTool是在使用Visual Web Developer 2005时,为了方便编译网站而写的一个小工具。


 

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


 

注意:下载的是一个工具包,安装后,从开始菜单中运行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 -781896338;  }  [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


 

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


 

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

Copyright© 2006-2015 ComponentCN.com all rights reserved.重庆磐岩科技有限公司(控件中国网) 版权所有 渝ICP备12000264号 法律顾问:元炳律师事务所
客服软件
live chat