Friday, August 21, 2009

ASP.NET MVC V2 Preview 1 Strongly Typed UI Helper Performance

I am sure many of you ASP.NET MVC enthusiasts have either examined, downloaded, or started to play around with the ASP.NET MVC V2 Preview 1 release.

One of the many new features is the concept of Strongly Typed UI Helpers such as Html.DisplayFor() & Html.EditorFor() which take a Lambda Expression as their argument. This is a great new feature because we can now get compile time checking of our arguments and it provides the ability to easily refactor changes across our project. The old version of the Html Helpers were littered with Magic Strings that were susceptible to error and painfully resistant to change.

I was talking to a co-worker at Sommet Group, Alex Robson, about the new Strongly Typed UI Helpers and he immediately became curious as to whether the helpers use Lambda Compiles to obtain the Model’s value. I checked the source code of the ASP.NET MVC V2 Preview 1 release and found the following code snippet from TemplateHelpers.cs…

internal static void TemplateFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, string templateName, string htmlFieldId, DataBoundControlMode mode) where TModel : class {
   object modelValue = null;
   try {
      modelValue = expression.Compile()(html.ViewData.Model);
   }
   catch (NullReferenceException) { }  // Okay if it throws a null ref exception, we can infer types without the actual data
   //Etc...
}

The reason for the concern is that he read a recent slide deck by Rudi Benkovic reviewing several performance issues of which Lambda Compiles were a hot topic.

The Experiment

With Alex’s experience developing the Open Source project Nvigorate, he has had a lot of experience working with Lamdas, Reflection, and the like. So, he started on a mission to compare various dynamic access methods and to compare their performance. The following graph provides a high level summary of Alex's findings. You can also read his detailed post from his blog and download his sample code. 

DynamicAccessMethodPerformance

As you can see from the graph above, the Lambda Compile is by far the least efficient way to obtain the model’s value when compared with Dynamic Methods, Type Descriptor, and Reflection. The worst part is that the Lambda Compile and Dynamic Method approaches both scaled poorly when the number of test cases increased.

Why Should I Care?

You might be thinking, “Who would actually use 1,000 or even 10,000 instances of the Html.EditorFor() anyway? Why does this really even matter?”.

The answer is that we shouldn’t just be concerned about the current page request, but also with the overall scalability of our website as the number of concurrent page requests increase.

What Now?

The good news is that the API for the Strongly Typed UI Helpers don’t need to change in order to accommodate these speed performances. All of Alex’s tests start with a Lambda Expression…

public static void Reflect<TModel, TProperty>(Expression<Func<TModel, TProperty>> expression, TModel instance)
{
    var val = typeof(TModel).GetProperty(GetMemberFromExpression(expression)).GetValue(instance, new object[] {});
    Debug.Assert(val.Equals("Dude"));
}

public static void TypeDescriptorCall<TModel, TProperty>(Expression<Func<TModel, TProperty>> expression, TModel instance)
{
    var properties = TypeDescriptor.GetProperties(typeof(TModel));
    var property = properties.Find(GetMemberFromExpression(expression), false);
    TProperty val = (TProperty)property.GetValue(instance);
    Debug.Assert(val.Equals("Dude"));
}

public static void DynamicMethodCall<TModel, TProperty>(Expression<Func<TModel, TProperty>> expression, TModel instance)
{
    var delegateCall = DynamicMethodFactory.CreateGetter(typeof(TModel).GetProperty(GetMemberFromExpression(expression)));
    var val = delegateCall(instance);
    Debug.Assert(val.Equals("Dude"));
}

public static void LambdaCompile<TModel, TProperty>(Expression<Func<TModel, TProperty>> expression, TModel instance)
{
    var val = expression.Compile()(instance);
    Debug.Assert(val.Equals("Dude"));
}

Summary

Based on some initial work done by Rudi Benkovic as seen in his slide deck, it came to our attention that Lamda Compiles can be very slow and actually affect website scaling when using ASP.NET MVC.

Thanks to Alex Robson, he compared 4 different dynamic access methods and compared their performance. His findings showed that using a Lamda Compile was the slowest of the 4 approaches tested when obtaining a Model’s value from a Lambda Expression. In contrast, Reflection was by far the fastest approach to consistently retrieve a Model’s value.

I hope the ASP.NET MVC team will take note of these findings and consider using Reflection instead of Compiling the Lamda inside of their next preview release.

No comments:

Post a Comment