New Bonsai and Fastest Binder

imageHi, I am very happy to say that I have new one bonsai tree :), I know I have them everywhere. This one will be grow up on my desk in my new work place. Reason for having bonsai trees and carry of them is because of my favorite team role that is the Plant and I like plants, especially bonsai trees as you all may already know. I want to say also that acting Plant/Creator role is difficult because I usually have many ideas and of course not all ideas are good. So I always need someone who help me evaluate my ideas for early drop this not useful one and prevent me for analyzing it and building prototype that probably will be only waste of my time. Anyway be Plant is most about putting idea like a plant into the ground and carry of the ideas that can grow. It is another nice aspect of care of small and beautiful tree. I like to say sometimes: “It is very easy to cut bonsai branch and almost impossible to put it back”. So before any change you need to be sure is it correct one or you need to take responsibility of wrong decision and live with that because there is no way back and the same is live. And I wonder have you got interesting hobby with similar quotes come from it? Oh, I almost forget to write about my small big success with KinectCam post that was visited more than 1000 times with 475 downaloads of New.KinectCam :).

Now after a bit of my philosophy I want to show you a nice way to create very fast Binder class. As you may already know in n-tier applications the user interface layer and the business logic layer are usually loosely connected by binding. And this is very good because binding is done by .NET reflection, so there is no strong connection between your logic class and user interface. Unfortunately binding typically uses property descriptors. So imaging you have following business POCO light state only C# class.

namespace Application.BussinessObjects.Entites
{
    using System;

    [Serializable]
    public class User {
        public string ID { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string EducationDegree { get; set; }
        public string Age { get; set; }
    }
}

It is typical entity that can be loaded and materialized by for example ORM like Entity Framework or NHibernate or even by home made the data access layer that use pure ADO.NET and IDataReader fast forward reader implementation of your favorite database provider. So it come to you and you can bind all properties of the User  class to your user interface using Binder class. It is and it was implemented in WinForms, WPF, WebForms and even ASP.NET MVC. So for example when you use DataBinder from System.Web.dll assembly of .NET 4.0 in the System.Web.UI namespace has following part.

private static bool enableCaching;

public static bool EnableCaching
{
    get
    {
        return DataBinder.enableCaching;
    }
    set
    {
        DataBinder.enableCaching = value;
        if (!value)
        {
            DataBinder.propertyCache.Clear();
        }
    }
}

private readonly static ConcurrentDictionary<type , PropertyDescriptorCollection>
propertyCache;

internal static PropertyDescriptorCollection GetPropertiesFromCache(object container)
{
    if (!DataBinder.EnableCaching || container is ICustomTypeDescriptor)
    {
        return TypeDescriptor.GetProperties(container);
    }
    else
    {
        PropertyDescriptorCollection properties = null;
        Type type = container.GetType();
        if (!DataBinder.propertyCache.TryGetValue(type, out properties))
        {
            properties = TypeDescriptor.GetProperties(type);
            DataBinder.propertyCache.TryAdd(type, properties);
        }
        return properties;
    }
}

public static object GetPropertyValue(object container, string propName)
{
    if (container != null)
    {
        if (!string.IsNullOrEmpty(propName))
        {
            object value = null;
            PropertyDescriptor propertyDescriptor =
            DataBinder.GetPropertiesFromCache(container).Find(propName, true);
            if (propertyDescriptor == null)
            {
                object[] fullName = new object[]
                { container.GetType().FullName, propName };
                throw new HttpException("DataBinder_Prop_Not_Found");
            }
            else
            {
                value = propertyDescriptor.GetValue(container);
                return value;
            }
        }
        else
        {
            throw new ArgumentNullException("propName");
        }
    }
    else
    {
        throw new ArgumentNullException("container");
    }
}

You can see whole implementation of this class by using the Telerik JustDecompile or the Red-Gate Reflector tool. Anyway it is small powerful class. And in this entry I want to show you how to create much faster implementation using FastMember library that you can get from NuGet. So I do not want to explain you all details of the property descriptors. I want to only write for you that it uses reflection and get it based of the Type class and I hope you see all details clearly. It is not a lot of code, isn’t? Now I will show you two classes and small benchamrk. First one will be WebDataBinder with the same behaviour like it is in build in .NET 4.5 Binder class and second one FastWebDataBinder with using the FastMember library.

namespace Application.BussinessObjects.Common
{
    using System;
    using System.Collections.Concurrent;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Web;
    // for FastWebDataBinder
    using FastMember;
    // for Main method benchamerk tests
    using Application.BussinessObjects.Entites;
    using System.Diagnostics;

    public static class WebDataBinder
    {
        private static bool enableCaching;

        public static bool EnableCaching
        {
            get
            {
                return WebDataBinder.enableCaching;
            }
            set
            {
                WebDataBinder.enableCaching = value;
                if (!value)
                {
                    WebDataBinder.propertyCache.Clear();
                }
            }
        }

        private readonly static ConcurrentDictionary<type , PropertyDescriptorCollection>
        propertyCache = new ConcurrentDictionary</type><type , PropertyDescriptorCollection>();

        internal static PropertyDescriptorCollection GetPropertiesFromCache(object container)
        {
            if (!WebDataBinder.EnableCaching || container is ICustomTypeDescriptor)
            {
                return TypeDescriptor.GetProperties(container);
            }
            else
            {
                PropertyDescriptorCollection properties = null;
                Type type = container.GetType();
                if (!WebDataBinder.propertyCache.TryGetValue(type, out properties))
                {
                    properties = TypeDescriptor.GetProperties(type);
                    WebDataBinder.propertyCache.TryAdd(type, properties);
                }
                return properties;
            }
        }

        public static object GetPropertyValue(object container, string propName)
        {
            if (container != null)
            {
                if (!string.IsNullOrEmpty(propName))
                {
                    object value = null;
                    PropertyDescriptor propertyDescriptor
                    = WebDataBinder.GetPropertiesFromCache(container).Find(propName, true);
                    if (propertyDescriptor == null)
                    {
                        object[] fullName = new object[]
                        { container.GetType().FullName, propName };
                        throw new HttpException("DataBinder_Prop_Not_Found");
                    }
                    else
                    {
                        value = propertyDescriptor.GetValue(container);
                        return value;
                    }
                }
                else
                {
                    throw new ArgumentNullException("propName");
                }
            }
            else
            {
                throw new ArgumentNullException("container");
            }
        }
    }

    public static class FastWebDataBinder
    {
        private static bool enableCaching;

        public static bool EnableCaching
        {
            get
            {
                return FastWebDataBinder.enableCaching;
            }
            set
            {
                FastWebDataBinder.enableCaching = value;
                if (!value)
                {
                    FastWebDataBinder.propertyCache.Clear();
                }
            }
        }

        private readonly static ConcurrentDictionary</type><type , TypeAccessor>
        propertyCache = new ConcurrentDictionary</type><type , TypeAccessor>();

        internal static TypeAccessor GetPropertiesFromCache(object container)
        {
            if (!FastWebDataBinder.EnableCaching || container is ICustomTypeDescriptor)
            {
                return TypeAccessor.Create(container.GetType());
            }
            else
            {
                TypeAccessor typeAccessor;
                Type type = container.GetType();
                if (!FastWebDataBinder.propertyCache.TryGetValue(type, out typeAccessor))
                {
                    typeAccessor = TypeAccessor.Create(type);
                    FastWebDataBinder.propertyCache.TryAdd(type, typeAccessor);
                }
                return typeAccessor;
            }
        }

        public static object GetPropertyValue(object container, string propName)
        {
            if (container != null)
            {
                if (!string.IsNullOrEmpty(propName))
                {
                    object value = null;
                    TypeAccessor typeAccessor
                    = FastWebDataBinder.GetPropertiesFromCache(container);
                    if (typeAccessor == null)
                    {
                        object[] fullName = new object[]
                        { container.GetType().FullName, propName };
                        throw new HttpException("DataBinder_Prop_Not_Found");
                    }
                    else
                    {
                        value = typeAccessor[container, propName];
                        return value;
                    }
                }
                else
                {
                    throw new ArgumentNullException("propName");
                }
            }
            else
            {
                throw new ArgumentNullException("container");
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var data = new List<user>();
            for (int p = 0; p < 1000000; ++p)
            {
                data.Add(new User
                {
                    ID = p.ToString(),
                    FirstName = "Piotr" + p,
                    LastName = "Sowa" + p,
                    EducationDegree = "MEng",
                    Age = ((p + 5) % 75).ToString()
                });
            }

            for (int i = 0; i < 10; ++i)
            {
                var meter = Stopwatch.StartNew();
                for (int p = 0; p < 1000000; ++p)
                {
                    var container = data[p];
                    var id = WebDataBinder
                             .GetPropertyValue(container, "ID");
                    var firstName = WebDataBinder
                                    .GetPropertyValue(container, "FirstName");
                    var lastName = WebDataBinder
                                   .GetPropertyValue(container, "LastName");
                    var educationDegree = WebDataBinder
                                          .GetPropertyValue(container, "EducationDegree");
                    var age = WebDataBinder
                              .GetPropertyValue(container, "Age");

                    var expectedId = p.ToString();
                    var expectedFirstName = "Piotr" + p;
                    var expectedLastName = "Sowa" + p;
                    var expectedEducationDegree = "MEng";
                    var expectedAge = ((p + 5) % 75).ToString();

                    var correct
                    = expectedId.Equals(id)
                    && expectedAge.Equals(age)
                    && expectedFirstName.Equals(firstName)
                    && expectedLastName.Equals(lastName)
                    && expectedEducationDegree.Equals(educationDegree);

                    if (!correct)
                    {
                        Console.WriteLine("Error");
                    }
                }
                meter.Stop();
                Console.WriteLine(
                "PropertyDescriptor: 5 millions reads of properties took {0}ms.",
                meter.ElapsedMilliseconds);

                var meterFast = Stopwatch.StartNew();
                for (int p = 0; p < 1000000; ++p)
                {
                    var container = data[p];
                    var id = FastWebDataBinder
                             .GetPropertyValue(container, "ID");
                    var firstName = FastWebDataBinder
                                    .GetPropertyValue(container, "FirstName");
                    var lastName = FastWebDataBinder
                                   .GetPropertyValue(container, "LastName");
                    var educationDegree = FastWebDataBinder
                                          .GetPropertyValue(container, "EducationDegree");
                    var age = FastWebDataBinder
                              .GetPropertyValue(container, "Age");

                    var expectedId = p.ToString();
                    var expectedFirstName = "Piotr" + p;
                    var expectedLastName = "Sowa" + p;
                    var expectedEducationDegree = "MEng";
                    var expectedAge = ((p + 5) % 75).ToString();

                    var correct
                    = expectedId.Equals(id)
                    && expectedAge.Equals(age)
                    && expectedFirstName.Equals(firstName)
                    && expectedLastName.Equals(lastName)
                    && expectedEducationDegree.Equals(educationDegree);
                    if (!correct)
                    {
                        Console.WriteLine("Error");
                    }
                }
                meterFast.Stop();
                Console.WriteLine(
                "FastMember:         5 millions reads of properties took {0}ms.",
                meterFast.ElapsedMilliseconds);
            }

            Console.WriteLine("Press any key to close...");
            Console.ReadKey(true);
        }
    }
}

And here is an output:

PropertyDescriptor: 5 millions reads of properties took 14812ms.
FastMember:         5 millions reads of properties took 1025ms.
PropertyDescriptor: 5 millions reads of properties took 14599ms.
FastMember:         5 millions reads of properties took 1027ms.
PropertyDescriptor: 5 millions reads of properties took 14595ms.
FastMember:         5 millions reads of properties took 993ms.
PropertyDescriptor: 5 millions reads of properties took 14624ms.
FastMember:         5 millions reads of properties took 991ms.
PropertyDescriptor: 5 millions reads of properties took 14567ms.
FastMember:         5 millions reads of properties took 1014ms.
PropertyDescriptor: 5 millions reads of properties took 14632ms.
FastMember:         5 millions reads of properties took 1010ms.
PropertyDescriptor: 5 millions reads of properties took 14618ms.
FastMember:         5 millions reads of properties took 1005ms.
PropertyDescriptor: 5 millions reads of properties took 14612ms.
FastMember:         5 millions reads of properties took 987ms.
PropertyDescriptor: 5 millions reads of properties took 14593ms.
FastMember:         5 millions reads of properties took 1006ms.
PropertyDescriptor: 5 millions reads of properties took 14605ms.
FastMember:         5 millions reads of properties took 1000ms.
Press any key to close...

And another one when I comment out checking if data is correct. Equals function is not very fast also you know.

PropertyDescriptor: 5 millions reads of properties took 14111ms.
FastMember:         5 millions reads of properties took 469ms.
PropertyDescriptor: 5 millions reads of properties took 13826ms.
FastMember:         5 millions reads of properties took 463ms.
PropertyDescriptor: 5 millions reads of properties took 13824ms.
FastMember:         5 millions reads of properties took 464ms.
PropertyDescriptor: 5 millions reads of properties took 13837ms.
FastMember:         5 millions reads of properties took 465ms.
PropertyDescriptor: 5 millions reads of properties took 13844ms.
FastMember:         5 millions reads of properties took 453ms.
PropertyDescriptor: 5 millions reads of properties took 13865ms.
FastMember:         5 millions reads of properties took 456ms.
PropertyDescriptor: 5 millions reads of properties took 13917ms.
FastMember:         5 millions reads of properties took 446ms.
PropertyDescriptor: 5 millions reads of properties took 13815ms.
FastMember:         5 millions reads of properties took 460ms.
PropertyDescriptor: 5 millions reads of properties took 13838ms.
FastMember:         5 millions reads of properties took 463ms.
PropertyDescriptor: 5 millions reads of properties took 13806ms.
FastMember:         5 millions reads of properties took 453ms.
Press any key to close...

And another one when I enabled caching.

PropertyDescriptor: 5 millions reads of properties took 2806ms.
FastMember:         5 millions reads of properties took 465ms.
PropertyDescriptor: 5 millions reads of properties took 2778ms.
FastMember:         5 millions reads of properties took 451ms.
PropertyDescriptor: 5 millions reads of properties took 2806ms.
FastMember:         5 millions reads of properties took 434ms.
PropertyDescriptor: 5 millions reads of properties took 2848ms.
FastMember:         5 millions reads of properties took 445ms.
PropertyDescriptor: 5 millions reads of properties took 2779ms.
FastMember:         5 millions reads of properties took 443ms.
PropertyDescriptor: 5 millions reads of properties took 2770ms.
FastMember:         5 millions reads of properties took 438ms.
PropertyDescriptor: 5 millions reads of properties took 2769ms.
FastMember:         5 millions reads of properties took 439ms.
PropertyDescriptor: 5 millions reads of properties took 2769ms.
FastMember:         5 millions reads of properties took 436ms.
PropertyDescriptor: 5 millions reads of properties took 2806ms.
FastMember:         5 millions reads of properties took 434ms.
PropertyDescriptor: 5 millions reads of properties took 2802ms.
FastMember:         5 millions reads of properties took 439ms.
Press any key to close...

So FastMember library based Binder is almost 30 times faster than classic property descriptor based one:). I know enabled caching helps with property descriptor, but usually we not cache property descriptors for types we want to bind because there are hundreds of them. There is one more thing. in .NET 4.5 reflection was tuned up about 4 times, so imagine that you are using .NET 4.0 based solution, that should increase performance with new binder about 120 times :). Enjoy!

P ;).

Leave a Reply

Your email address will not be published. Required fields are marked *

*

This site uses Akismet to reduce spam. Learn how your comment data is processed.