A Faster TestTypeResolutionService


In February, I wrote about unit testing CodeDomSerializers, but although it was a good initial attempt, there was room for improvement (as I also described in the article). Back then, my main priority was just to get my unit tests working, so other issues were less important to me at the time.


Since then, I’ve used this method quite a bit, and although I’ve been generally happy with it, test performance was not good. Each serialization test caused my CPU utilization to jump to 100% and took perhaps 5-10 seconds to perform. When you have a dozen of these tests, running a test suite takes several minutes, which works against the goal of agile development that you should refactor and run unit tests, refactor and run unit tests, etc.


I knew exactly from were my performance issue originated: The GetType method of the TestTypeResolutionService loops through all referenced assemblies looking for a type with the specified type name. This is quite an expensive operation, as some of the referenced assemblies (like mscorlib and System) contain thousands of types.


However, often the name input parameter contains the assembly qualified type name, and if this is the case, Type.GetType is quite a lot faster. Using Type.GetType as the first attempt to resolve the type, and only looping through referenced assemblies as a second attempt dramatically improves test performance. Here’s the updated method:


public Type GetType(string name, bool throwOnError,
    bool ignoreCase)
{
    Type returnType = Type.GetType(name, false, ignoreCase);
    if (returnType != null)
    {
        return returnType;
    }
 
    AssemblyName[] assemblyNames =
        Assembly.GetExecutingAssembly().
        GetReferencedAssemblies();
    foreach (AssemblyName an in assemblyNames)
    {
        Assembly a = Assembly.Load(an);
        Type[] types = a.GetTypes();
        foreach (Type t in types)
        {
            if (t.FullName == name)
            {
                this.cachedTypes_[name] = t;
                return t;
            }
        }
    }
 
    if (throwOnError)
    {
        throw new ArgumentException();
    }
    return null;
}

Type.GetType is called with the throwOnError parameter set to false, which causes the method to return null if the type could not be resolved. This simple change has caused my serialization tests to run 5-15 times faster than before, and running the whole test suite is now something that takes approximately 10 seconds.


If you find it difficult to reproduce the TestTypeResolutionService class from my disparate blog entries, I’ve included it as an attachment to this post.

TestTypeResolutionService.cs

Comments (3)

  1. leppie says:

    Several problems with the code snippet.

    – Cache is not checked initially

    – Initial case dont add type to cache

    – Latter cases lacks case sensitivity (causing cache and lookups to fail)

    Also Assembly.GetTypes() is a bit overkill when you really need Assembly.GetExportedTypes() only.

    :)

  2. ploeh says:

    Hi leppie

    Thank you for your comments. You are right, my cache code seems a bit rudimentary :)

    Actually, the initial use of Type.GetType improved performance so much that the cache is not necessary any more, so now I’ve just removed it completely, and I there is no noticable difference.

    You are right that I only need Assembly.GetExportedTypes. Although the change doesn’t make for any noticable difference in performance, it’s the more correct thing to do.

    The method now looks like this:

    public Type GetType(string name, bool throwOnError, bool ignoreCase)
    {
        Type returnType = Type.GetType(name, false, ignoreCase);
        if (returnType != null)
        {
            return returnType;
        }
     
        AssemblyName[] assemblyNames = Assembly.GetExecutingAssembly().GetReferencedAssemblies();
        foreach (AssemblyName an in assemblyNames)
        {
            Assembly a = Assembly.Load(an);
            Type[] types = a.GetExportedTypes();
            foreach (Type t in types)
            {
                if (t.FullName == name)
                {
                    return t;
                }
            }
        }
     
        if (throwOnError)
        {
            throw new ArgumentException();
        }
        return null;
    }

    I have updated the attached code file to reflect this.

  3. ploeh blog says:

    When writing complex components or controls, it is sometimes necessary to implement custom CodeDOM serialization…