XmlSerializer Constructor Performance Issues

June 12th 2011 Serialization .NET Framework

XmlSerializer is often a great choice for persisting objects or transmitting them over the wire, either by using default object serialization tailored only with attributes or by implementing the IXmlSerializable interface yourself. If you're not careful though, this might come at a significant performance cost.

You're probably already aware that in order to increase performance of XML serialization, assemblies are dynamically generated each time XmlSerializer is instantiated for the first time for a specific type. Since it happens only once in the application lifetime, it usually doesn't pose much of a problem.

What you might not know, is that this is not necessary a once in a lifetime process. If you're using the wrong constructor, it can happen every time you instantiate XmlSerializer. To be fair, this is mentioned in the documentation, but if you're like me, you might not read it until you encounter performance issues in your project. So unless you're calling XmlSerializer(Type) or XmlSerializer(Type, String), the dynamically generated assemblies are not reused. When you think about it, it also makes perfect sense. All other constructors modify the behavior of the serializer, thus causing small differences in the generated serializers and hindering their reuse.

Fortunately there is a way to avoid this performance hit. Since XmlSerializer is one of the few thread safe classes in the framework you really only need a single instance of each serializer even in a multithreaded application. The only thing left for you to do, is to devise a way to always retrieve the same instance. If you know in advance which serializers you'll need, you can simply store them in static fields. If you don't, you'll need to store them in a dictionary, keyed by the arguments used in the constructor.

Your best choice for the data structure is ConcurrentDictionary introduced in .NET 4 which takes care of synchronizing for multithreading. This makes the implementation really simple:

private static ConcurrentDictionary<Type, XmlSerializer> serializers = 
    new ConcurrentDictionary<Type, XmlSerializer>();

private XmlSerializer GetSerializer(Type type)
{
    return serializers.GetOrAdd(type, t => 
        { return new XmlSerializer(t, new XmlRootAttribute("Value")); });
}

If you're wondering what kind of performance hit I'm talking about, try running the following sample:

static void Main(string[] args)
{
    StringWriter writer = new StringWriter();
    XmlTextWriter xmlWriter = new XmlTextWriter(writer);
    xmlWriter.WriteStartDocument();
    xmlWriter.WriteStartElement("Root");
    Stopwatch stopwatch = new Stopwatch();
    int iterations = 100;

    stopwatch.Restart();
    for (int i = 0; i < iterations; i++)
    {
        XmlSerializer serializer = new XmlSerializer(typeof(string));
        serializer.Serialize(xmlWriter, "Test");
    }
    stopwatch.Stop();
    Console.WriteLine(
        "{0} serializations with XmlSerializer(string):                   {1,5} ms",
        iterations, stopwatch.ElapsedMilliseconds);

    stopwatch.Restart();
    for (int i = 0; i < iterations; i++)
    {
        XmlSerializer serializer = 
            new XmlSerializer(typeof(string), new XmlRootAttribute("Type"));
        serializer.Serialize(xmlWriter, "Test");
    }
    stopwatch.Stop();
    Console.WriteLine(
        "{0} serializations with XmlSerializer(string, XmlRootAttribute): {1,5} ms",
        iterations, stopwatch.ElapsedMilliseconds);

    XmlSerializer staticSerializer = 
        new XmlSerializer(typeof(string), new XmlRootAttribute("Type"));
    stopwatch.Restart();
    for (int i = 0; i < iterations; i++)
    {
        staticSerializer.Serialize(xmlWriter, "Test");
    }
    stopwatch.Stop();
    Console.WriteLine(
        "{0} serializations with a static serializer:                     {1,5} ms",
        iterations, stopwatch.ElapsedMilliseconds);

    Console.ReadKey();
}

In my case the output was as follows:

100 serializations with XmlSerializer(string):                       2 ms
100 serializations with XmlSerializer(string, XmlRootAttribute): 17460 ms
100 serializations with a static serializer:                         2 ms

Quite a difference, isn't it?

Copyright
Creative Commons License