Performance bottleneck Url.Action - can I workaround it?
Asked Answered
P

3

8

I have an application that I recently upgraded from ASP.NET MVC1 to ASP.NET MVC4 rc1.

It uses the Webforms viewengine.

It has performance issues whenever Url.Action(action,controller) is used.

I can reproduce the issue in ASP.NET MVC3.

I need 3ms to render views that have 10 instances of the Url.Action helper in it in ASP.NET MVC1 and 40ms to render the same in ASP.NET MVC3.

I already found some ways to make it render faster:

  • I moved the default route to the top

  • I removed Url.Action and used static links

This does not feel right: the application is pretty large and I need the goodness of a decent working routing in it. I am also not confident that I found all performance bottlenecks. Routing is a central part of MVC: if there is something performing badly it will pop up in different parts of the application.

I have the impression that MVC3 introduced some routing features (like regex constraints) that even if I dont use them lead to a badly performing application.

Is there something I can do like turning of features of routing or using a different set of URL-helpers?

This code reproduces the issue:

Index action

public ActionResult Index()
        {

            return View();
        }

index.aspx

<%@ Page Language="C#"  Inherits="System.Web.Mvc.ViewPage" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head >
    <title></title>
    <link href="../../Content/Site.css" rel="stylesheet" type="text/css" />
</head>

<body>
    <div class="page">
<%= Url.Action("Action1", "Controller1") %>
<%= Url.Action("Action2", "Controller2") %>
<%= Url.Action("Action3", "Controller3") %>
<%= Url.Action("Action4", "Controller4") %>
<%= Url.Action("Action5", "Controller5") %>
<%= Url.Action("Action6", "Controller6") %>
<%= Url.Action("Action7", "Controller7") %>
<%= Url.Action("Action8", "Controller8") %>
<%= Url.Action("Action9", "Controller9") %>
<%= Url.Action("Action10", "Controller10") %>
    </div>
</body>
</html>

Route registration This looks strange: but I just want to simulate my not very complicated routing. This is not the 600 routes of SO!

public static void RegisterRoutesSlow(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
    routes.IgnoreRoute("{language}/Content/{*pathInfo}");

    routes.IgnoreRoute("images/{*pathinfo}");
    routes.IgnoreRoute("scripts/{*pathinfo}");
    routes.IgnoreRoute("content/{*pathinfo}");
    routes.IgnoreRoute("{file}.gif");
    routes.IgnoreRoute("{file}.jpg");
    routes.IgnoreRoute("{file}.js");
    routes.IgnoreRoute("{file}.css");
    routes.IgnoreRoute("{file}.png");
    routes.IgnoreRoute("{file}.pdf");
    routes.IgnoreRoute("{file}.htm");
    routes.IgnoreRoute("{file}.html");
    routes.IgnoreRoute("{file}.swf");
    routes.IgnoreRoute("{file}.txt");
    routes.IgnoreRoute("{file}.xml");
    routes.IgnoreRoute("{*favicon}", new { favicon = @"(.*/)?favicon.ico(/.*)?" });

    for (int i = 0; i <= 10; i++)
    {
        routes.MapRoute(
            // Route name
            "RouteName" + i.ToString(),
            // URL with parameters                              
            "{language}/{controller}/{action}/{para1}",
            // Parameter defaults
            new
            {
                action = "Index",
                language = "de",
                para1 = 0
            },
            //Parameter constraints
            new { language = "de|en", controller = "SomeNameOfAnActualController" + i.ToString() }
            );
    }
    routes.MapRoute(
                   "DefaulRoute",            // Route name
                   "{controller}/{action}",    // URL with parameters
                   new
                   {
                       controller = "Home",
                       action = "Index",
                   }
               );
    routes.MapRoute("404-PageNotFound", "{*url}", new { controller = "Error", action = "PageNotFound", language = "de" });
}

EDIT

The sample code was compiled against MVC2 now. In VS2010 MVC2 can be compiled against .NET 3.5 or 4.0.

The performance with 3.5 is good and 4.0 is bad.

I guess this means that the poorly performing part is not in a MVC assembly but in a framework assembly (like System.Web.Routing.dll). The question is still the same: Can I do something about it? An accepted answer would also be: No, the code is slow because from version 3.5 to 4.0 MS changed XXX

EDIT-2

I decompiled the part of System.Web.Routing.dll that takes to long. It uses a compiled regular expression. There is a code path (constraint2.Match ) that returns without executing the regex, but I did not check yet if it internally uses a different expensive operation.

protected virtual bool ProcessConstraint(HttpContextBase httpContext, object constraint, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
    object obj2;
    IRouteConstraint constraint2 = constraint as IRouteConstraint;
    if (constraint2 != null)
    {
        return constraint2.Match(httpContext, this, parameterName, values, routeDirection);
    }
    string str = constraint as string;
    if (str == null)
    {
        throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture, SR.GetString("Route_ValidationMustBeStringOrCustomConstraint"), new object[] { parameterName, this.Url }));
    }
    values.TryGetValue(parameterName, out obj2);
    string input = Convert.ToString(obj2, CultureInfo.InvariantCulture);
    string pattern = "^(" + str + ")$";
    return Regex.IsMatch(input, pattern, RegexOptions.CultureInvariant | RegexOptions.Compiled | RegexOptions.IgnoreCase);
}
Push answered 9/8, 2012 at 21:38 Comment(8)
Does this happen on the first request or everytime ?Davy
The first request is slower, the time i meassured is the second request. And it is all in "release" mode.Push
Just out of curiosity have you tried it without all the IgnoreRoute instructions?Centralism
@Centralism good idea: It realy changes the performance. But even without the ignore routes there remains a significant performance hit when using URL.ActionPush
There are solved problem similar to yours: [First call to Url.Action on a page is slow][1] [1]: #11900634Candlemaker
@Kirill the tine is not spent on compiling the views. The I dont measure the first response.Push
If you read accepted answer, there are conclusion about routing constraints with regexp constraintsCandlemaker
@Kirill This looks promising. Can you post it as an answer?Push
C
3

There are solved problem similar to yours: First call to Url.Action on a page is slow there are conclusion about routing constraints with regexp constraints that is very slow.

Candlemaker answered 17/8, 2012 at 21:56 Comment(1)
Using my own implementation of IRouteConstraint like described in the accepted answer solved the problem.Push
P
0

Each view is compiled and cached when it is used the first time. However, since the aspx Views were not designed specifically for Mvc each Url.Action is not compilede once for all in the final link but it is re-computed at each execution. Razor compiler has better optimization. The only solution is computing the various linkks with Url.Action and storing them into some application level property, so it is computed just at first execution. You can put them either in the Application dictionary, or in static properties of a class.

Piccaninny answered 14/8, 2012 at 8:17 Comment(0)
H
0

I am not sure to the cause of what you are seeing, but it may not only be an MVC 1 vs MVC 4, IIS setup in later versions can impact the speed of view rendering. I came across a slide deck a few months ago that I thought was pretty interesting, relating to performance improvement tips in MVC 3 apps.

http://www.slideshare.net/ardalis/improving-aspnet-mvc-application-performance

Specifically, take a look at slide 28, which states:

Uninstall IIS UrlRewrite Module

  • If no applications on the server are using it
  • No effect in MVC apps before v3
  • Enhances speed of URL generation

I take this to mean that the UrlRewrite module will negatively impact MVC 3, but not MVC 2 or 1, which could be a source of the slowdown that you are seeing. There are some other improvements as well, but I don't believe that any of them 'directly' relate to what you are seeing.

Heeler answered 14/8, 2012 at 13:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.