在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
今天与大家分享:一种优化页面执行速度的方法。 为了让您对优化的效果有个直观的了解,我准备了下面的测试结果截图: 测试环境: 二个红框中的数字反映了优化前后的执行时间。 本文的测试结果也仅仅只是一个参考数字,这个结果也只是根据我所设计的测试页面得出的。 回到顶部
看过了优化结果,再来介绍一下:这个测试到底是在测试什么东西? 现在有很多做ASP.NET的开发人员,应该都是从ASP.NET的WebForm编程模型开始学习的。大家都很喜欢用服务器控件,不管输出什么,都会使用服务器控件。有时候为了让页面呈现干净的HTML代码,有些人会选择使用Repeater,Literal这类简单的服务器控件。或许有些人认为:我已不使用GridView这样强大复杂的控件,页面执行速度已经很快了。 真是这样吗? 今天测试的起点就从使用简单的服务器开始,我会分二次对它做一系列的性能优化。
在继续介绍之前,有一点我想有必要说明一下: 优化的过程涉及到ASP.NET服务器控件的使用,测试结果也仅仅只是一个参考数字。 回到顶部
测试方法在这次优化过程中,我并没有设计很复杂的测试页面,而是一个很简单的测试页面,页面显示效果如下: 这个页面其实就是显示了一堆超链接,它们来自于我的博客侧边栏的【推荐排行榜】,总共有20条记录,我让页面重复5次输出,也就是生成了100个超链接。 测试的数据是这样获取的: 然后,网站在初始化时,从这段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访问页面是个很简单的方法,但我并不打算这样做。 说明:为了测试代码写起来简单,我使用了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的页面编译过程是个复杂的操作,其实我们可以不用关心页面是如何编译的, 为了能直观地了解页面编译后的样子,我编译了整个网站,并生成到一个DLL文件中,然后使用Reflector.exe来分析这个DLL的源代码。 将网站编译成一个DLL文件有二个方法: 在编译网站之后,我就可以知道网站在运行时如何运行页面了。 测试用例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中。 测试用例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的编译结果完全不同。 通过前面的分析,您现在明白了为什么二个页面的执行速度相差6倍了原因了吧。 好像还有一点没有解释:__Render__control1如何被调用? 我们都知道:以ASPX页面为代表的WebForm编程模型在执行时有一个特点:递归循环每个控件。 的确,很多书籍以及技术资料都是说:在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语句块呢? 此 API 支持 .NET Framework 基础结构,不适合在代码中直接使用。 回到顶部
测试用例3:InlineUserControl.ascx在测试用例3中,我将页面中用于输出的代码移到一个用户控件中。 测试代码: [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 再次申明:测试结果也仅仅只是一个参考数字。 |
请发表评论