各种URL生成方式的性能对比
在上一篇文章中我們列舉了各種URL生成的方式,其中大致可以分為三類:
我們可以輕易得知,這3種作法可維護性依次增加,而性能依次減少。不過,我們還是有一個疑問,這個性能究竟相差多少?它是否的確真的可以被忽略?為此,我們還是來進行一次性能對比吧。
測試對象
為了獲得貼近實際的測試結果,我打算以我的博客首頁作為測試對象。您可以發現,這個頁面上的鏈接非常多,我把它分為三個部分:
作為一個演示,我也精心準備了四種URL模式,它們分別是:
// 博客首頁 routes.MapRoute("Blog.Index","{blog}",new { controller = "Blog", action = "Index" });// 標簽頁 routes.MapRoute("Blog.Tag","{blog}/tag/{tag}",new { controller = "Blog", action = "Tag" });// 按月歸檔頁 routes.MapRoute("Blog.Archive","{blog}/archive/{year}/{month}.html",new { controller = "Blog", action = "Archive" });// 文章詳細頁 routes.MapRoute("Blog.Post","{blog}/archive/{*post}",new { controller = "Blog", action = "Post" });以上代碼在Web項目中的GlobalApplication.cs文件中。您可以發現,我完全按照博客園在定制URL的模式。我想說明的是,其實URL Routing完全非常靈活,您可以根據需求使用各種形式的URL,關鍵只是“規則配置”而已。不過雖然配置了4種Route規則,但是我只實現了BlogController下的一個Action:博客首頁(Index),如下:
[RouteName("Blog.Index")] public ActionResult Index([ModelBinder(typeof(BlogBinder))]Blog blog, string view) {var model = new IndexModel { Blog = blog, Posts = GetPosts() };return View("Index" + view, model); }private static List<Post> GetPosts() {... }[RouteName("Blog.Post")] public ActionResult Post([ModelBinder(typeof(BlogBinder))]Blog blog,[ModelBinder(typeof(PostBinder))]Post post) {throw new NotImplementedException(); }[RouteName("Blog.Tag")] public ActionResult Tag([ModelBinder(typeof(BlogBinder))]Blog blog,[ModelBinder(typeof(StringBinder))]string tag) {throw new NotImplementedException(); }[RouteName("Blog.Archive")] public ActionResult Archive([ModelBinder(typeof(BlogBinder))]Blog blog,int year,int month) {throw new NotImplementedException(); }BlogController.cs文件處于Web.Controllers項目中。我為每個復雜參數都安排了ModelBinder,具體實現都很簡單,您可以下載文末的代碼進行瀏覽。在GetPosts里我將準備40個Post對象,每個Post對象分配5個Tag,這些都將顯示在頁面上。Index方法的參數view通過Query String進行傳遞,例如,您可以通過一下三個鏈接來訪問不同的URL生成方式:
- /jeffz?view=ByRaw:使用拼接字符串的方式生成URL
- /jeffz?view=ByRoute:使用Route規則生成URL
- /jeffz:使用Lambda表達式這個“推薦方式”生成URL
三種方式
在Web.UI項目中的Views目錄下有BlogController所使用的三個視圖模板,他們使用不同的方式來生成完全一樣的內容。例如Index.aspx文件中的定義是這樣的:
<!-- 主體文章列表,40篇,各5個Tag --> <h2>Posts</h2> <ul><% foreach (var post in Model.Posts) { %> <li>Title: <a href="<%= Url.ToPost(Model.Blog, post) %>"><%= Html.Encode(post.Title) %></a>Tag: <% foreach (var tag in post.Tags) { %> <a href="<%= Url.ToTag(Model.Blog, tag) %>"><%= Html.Encode(tag) %></a> | <% } %> </li><% } %> </ul><!-- 邊欄文章列表,共計120篇 --> <h2>More post links</h2> <ul><% for (int i = 0; i < 3; i++) { %> <% foreach (var post in Model.Posts) { %> <li style="display: inline;"><a href="<%= Url.ToPost(Model.Blog, post) %>"><%= Html.Encode(post.Title) %></a> |</li><% } %> <% } %> </ul><!-- 歸檔列表,3年共計36個鏈接 --> <h2>Archives</h2> <ul><% for (int year = 2007; year <= 2009; year++) { %> <% for (int month = 1; month <= 12; month++) { %> <li><a href="<%= Url.ToArchive(Model.Blog, year, month) %>"><%= year %>年<%= month %>月</a></li><% } %> <% } %> </ul>對于IndexByRaw.aspx和IndexByRoute.aspx來說,它們只是把ToPost,ToTag等方法改為對應的ToPostByRaw或ToTagByRoute而已。因此,其實生成URL的關鍵還在于這些輔助方法。例如ToPost,ToTag和ToArchive三個擴展方法是這樣實現的:
public static string ToPost(this UrlHelper helper, Blog blog, Post post) {return helper.Action<BlogController>(c => c.Post(blog, post)); }public static string ToTag(this UrlHelper helper, Blog blog, string tag) {return helper.Action<BlogController>(c => c.Tag(blog, tag)); }public static string ToArchive(this UrlHelper helper, Blog blog, int year, int month) {return helper.Action<BlogController>(c => c.Archive(blog, year, month)); }可見,使用Lambda表達式構造URL的代碼非常清晰,簡單,直觀——因為Action輔助方法會自動從Lambda表達式中提取Controller和Action名,并調用每個參數的RouteBinder實現復雜類型參數的雙向轉化,它不需要我們關心更多的東西。
而如果直接拼接字符串,那么它可能就是這樣的:
public static string ToTagByRaw(this UrlHelper helper, Blog blog, string tag) {return blog.Alias + "/tag/" + HttpUtility.UrlEncode(tag); }而基于Route構造URL就會顯得略麻煩一些:
public static string ToTagByRoute(this UrlHelper helper, Blog blog, string tag) {var path = helper.RouteCollection.GetVirtualPathEx(helper.RequestContext,"Blog.Tag",new RouteValueDictionary{{ "controller", "Blog" },{ "action", "Tag" },{ "blog", blog.Alias },{ "tag", HttpUtility.UrlEncode(tag) }});return path.VirtualPath; }至于后兩種方式的其它幾個輔助方法,您可以下載文末的代碼進行瀏覽,它們都在Web.Controllers項目中的UrlGenExtensions.cs文件中。
運行測試
我們使用BlogController中另一個Action方法:Benchmark進行性能測試。Benchmark方法接受兩個參數,一個是循環次數,而另一個則是測試目標:
public ActionResult Benchmark(int iteration, string view) {var model = new IndexModel{Blog = new Blog { Alias = "jeffz" },Posts = GetPosts()};var result = new BenchmarkModel{Iteration = iteration,View = "Index" + view};var viewInstance = new WebFormView("~/Views/Blog/Index" + view + ".aspx");var viewContext = new ViewContext(this.ControllerContext,viewInstance,new ViewDataDictionary(model),new TempDataDictionary());// warm upviewInstance.Render(viewContext, new StringWriter());GC.Collect();var watch = new Stopwatch();watch.Start();for (int i = 1; i <= iteration; i++){viewInstance.Render(viewContext, new StringWriter());if (i % 100 == 0){result.Add(i, watch.Elapsed);}}watch.Stop();return View(result); }于是,您可以使用下面的鏈接觀察使用三種方法生成1000次頁面所消耗的時間:
- /benchmark?iteration=1000&view=ByRaw:使用拼接字符串的方式生成URL
- /benchmark?iteration=1000&view=ByRoute:使用Route生成URL
- /benchmark?iteration=1000:使用Lambda表達式生成URL
Benchmark方法會每隔100次記錄一下結果,因此上面的鏈接加載完后會出現10條信息——這便是我們得到的結果。
結果
至于最終的結果以及分析,我打算暫時賣個關子,不多久我就會獨立開篇進行說明的。您可以在這里下載到整個解決方案,代碼不多,但也花費了我2個小時進行準備,您可以親自試驗一下。您直接使用上面的Benchmark鏈接進行觀察即可,生成1000次頁面已經足以展示一些問題了——不過在此之前,您不妨進行一個預測,猜猜看它們之間究竟有多大的性能差距。
相關文章
- 各種URL生成方式的性能對比
- 各種URL生成方式的性能對比(結論及分析)
- 為URL生成設計流暢接口(Fluent Interface)
- URL生成方式性能優化結果
- Route組件GetVirtualPath方法性能優化結果
總結
以上是生活随笔為你收集整理的各种URL生成方式的性能对比的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 梦到好多大狗追着咬人
- 下一篇: 做梦梦到摘莲子是什么意思