Web developer from Sydney Australia. Currently using asp.net, mvc where possible.

Monday, May 17, 2010

Quickly Trim all your model's string properties – Speed results

Update: Posted another follow up with feedback on the usage of Fasterflect

With my Quickly Trim all your model's string properties post causing quite a stir (2 comments), I have decided to post a follow up and actually test the speed.

For the actual benchmarking I used a slightly modified version of this nice little class http://www.yoda.arachsys.com/csharp/Benchmark.cs, which I found from this page http://www.yoda.arachsys.com/csharp/benchmark.html

I tested 4 different methods that use reflection to trim all the string properties on an object and the 'long hand' method that does not use reflection at all.

Test Methods:

    1. Long Hand - No reflection.
    2. Initial code - with the GetIndexParameters call removed (not needed)
    3. Initial code with linq – Initial code but using linq to select the properties to process
    4. TypeDescriptor– used the TypeDescriptor class to get the property list
    5. FasterFlect m1 - Uses the FasterFlect library
    6. FasterFlect m2 – Uses the FasterFlect library’s delegates approach

Results

So here are the results (over 1 million object trims):
Long Hand Trim 00:00:01.4220000
Initial code 00:00:07.7130000
Initial code with linq 00:00:10.7270000
TypeDescriptor 00:00:14.2340000
FasterFlect m1 00:00:06.1330000
FasterFlect m2 00:00:06.1830000

Surprised? I was. Firstly, the refection code varied from 7 - 14 times slower then the direct code, this wasn't a surprise more of a reminder that you really need to be careful when using reflection in your code base.

I knew my initial code would perform better then trying to filter for properties before doing the work. Filtering the properties first using linq or any other means, basically results in another loop over the properties on the object. So the more properties you have on your object the worst this method will perform.

But I was quite suprised with the TypeDescriptor. I had hopes that this would perform better then my initial code, as according to the documentation this method actually caches the meta data about objects. I am suspicious I haven’t used it to its full potential…

The other surprise was that the delegate method in fasterflect is actually slower then the normal method. Again, someone may well point out how to implement this better. The other note is that fasterflect was only able to achieve an 80% reduction. I was hoping for more.

Anyway interesting stuff, if someone has another fast method to achieve the same results let me know I’ll take it for a test drive.
Download the source code.

Long Hand code


Initial code



Initial code with linq



TypeDescriptor



FasterFlect Method #1



FasterFlect Method #2



kick it on DotNetKicks.com

4 comments:

Chris Fulstow said...

Selecting the properties first with LINQ shouldn't mean another loop. The query will just return an Iterator that defers execution until the foreach loop.

Mark Kemper said...

Good point Chris. However it's still much slower then then the plain old for loop.

Chris Fulstow said...

How about caching the property info for each type? Something like:

private readonly static Dictionary PropertyCache =
new Dictionary();

private readonly static object Mutex = new object();

public static void NullSafeTrimStrings(this T item)
{
var type = typeof(T);
PropertyInfo[] properties;
if (!PropertyCache.TryGetValue(type, out properties))
{
properties =
(from p in typeof (T).GetProperties()
where
p.CanRead && p.CanWrite &&
p.PropertyType == typeof (string)
select p).ToArray();

lock (Mutex)
{
if (!PropertyCache.ContainsKey(type))
PropertyCache.Add(type, properties);
}
}

foreach (var property in properties)
{
var currentValue = (string)property.GetValue(item, null);
if (currentValue != null)
property.SetValue(item, currentValue.Trim(), null);
}
}

Mark Kemper said...

@Chris, yep that's what I did in the next blog post (See the link at the top of this page)

Only thing is I didn't use a lock object, basically I figured if 1 or more threads attempt to set the dictionary value at the same time, then so be it. They will always set it to the same thing anyway.