Improving Performance of XML Serializers in .Net


Problem background

.Net infrastructure makes it very easy to define and use strongly typed wrappers (XML serializes) to read from and write to an XML document. Default implementation of this support comes with a performance penalty that your program pays at run-time. By default, at runtime, .Net infrastructure will generate a serializer class on the fly, compile it using C# compiler into a temporary assembly, load the assembly and then use the generated class. Startup time cost of doing so is obvious, and may not be acceptable in many scenarios. In addition if compiler is not available in production (not installed, or disabled to run by policy), application will simply fail. This article shows how to address the problem by explicitly generating serialization assemblies and shipping them along with your application.

Creating sample project

Lets start by creating a sample project that uses XML serialization and demonstrating the problem. In Visual Studio go to File/New/Project…, select Visual C#/Windows/Console Application. Input “XmlSerializerLab” for the project name and hit OK.

Now that we have generated default console template let add very simple code that creates and uses XML serialization infrastructure. Below are the modifications to generated Program.cs with explanations:

1:  using System;   
2:  using System.IO;   
3:  using System.Xml.Serialization;   
4:      
5:      
6:  namespace XmlSerializerLab   
7:  {   
8:   [XmlRootAttribute("MyRoot")]   
9:   public class MyXmlRoot  
10:   {  
11:   }  
12:     
13:   class Program  
14:   {  
15:     static void Main(string[] args)  
16:     {  
17:       // create strongly typed content  
18:       MyXmlRoot root = new MyXmlRoot();  
19:     
20:       // create serializer  
21:       var serializer = new XmlSerializer(  
22:         typeof(MyXmlRoot));  
23:     
24:       // serialize  
25:       var ms = new MemoryStream();  
26:       serializer.Serialize(ms, root);  
27:     
28:       // verify serialized XML  
29:       ms.Position = 0;  
30:       Console.WriteLine(  
31:         new StreamReader(ms).ReadToEnd());  
32:      }  
33:    }  
34:  }

Lines 2-3 import namespaces for XML serializer and stream support

Lines 8-11 define the simplest possible class to represent our XML document. We only represent the top level element which should suffice for our test.

Line 18 creates an instance of strongly typed class that we will serialize using framework

Lines 20-26 serialize our instance into memory stream

Lines 28-31 print the content of the memory stream to standard output so we can eyeball the resulting XML content

If you run your application now you should see something similar to:

image

Verifying the default implementation

Now we can add some code to verify that .Net runtime tries to use pre-generated serializers before generating and compiling them on the fly. By default .Net runtime will look for serializer in “AssemblyName.XmlSerializers.dll”, where “AssemblyName” is the name of the assembly that contains the actual class being serialized (MyXmlRoot in our example). To verify we will listen to domain’s AssemblyResolve event and print all the assemblies that .Net loader tried to locate but failed.

1:  using System;   
2:  using System.IO;   
3:  using System.Xml.Serialization;   
4:      
5:      
6:  namespace XmlSerializerLab   
7:  {   
8:   [XmlRootAttribute("MyRoot")]   
9:   public class MyXmlRoot  
10:   {  
11:   }  
12:     
13:   class Program  
14:   {  
15:     static void Main(string[] args)  
16:     {  
17:       AppDomain.  
18:         CurrentDomain.AssemblyResolve +=  
19:         (sender, e) =>  
20:       {  
21:         Console.WriteLine("Not found: {0}",  
22:           e.Name);  
23:         return null;  
24:       };  
25:         
26:       // create strongly typed content  
27:       MyXmlRoot root = new MyXmlRoot();  
28:     
29:       // create serializer  
30:       var serializer = new XmlSerializer(  
31:         typeof(MyXmlRoot));  
32:     
33:       // serialize  
34:       var ms = new MemoryStream();  
35:       serializer.Serialize(ms, root);  
36:     
37:       // verify serialized XML  
38:       ms.Position = 0;  
39:       Console.WriteLine(  
40:         new StreamReader(ms).ReadToEnd());  
41:      }  
42:    }  
43:  }

Lines 17-24 subscribe to AssemblyResolve event and output the name of the assembly being located to the console. If you run application now, you will see that .Net loader was trying to load serialization assembly for MyXmlRoot class twice. First it attempted to locate an assembly using its strong name (XmlSerializerLab.XmlSerializers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null) and then assuming the assembly is not signed (XmlSerializerLab.XmlSerializers).

Solution

As I mention in the very beginning of the article solution is really simple: we need to pre-generate serialization assemblies at build time and ship them with our application. Please note that Visual Studio has a project attribute “Generate serialization assembly” in project properties/Build tab/Output section. Contrary to what its name implies, it won’t do you any good, unless you project contains Web service proxies. I’m not sure if this is by design or simply a bug, but for everything else adding an explicit post-build script (sgen /a:”$(TargetPath)” /force) will do it. Make sure that “sgen” utility is in the PATH or its full path is explicitly resolved. Alternatively you can run the command manually from Visual Studio command line. Start VS command line, change your active folder to our project’s bin/Debug and run the following:

image

If you examine Debug folder now, you will find that “XmlSerializerLab.XmlSerializers.dll” was created. Run you application now. You will see that no assembly resolution events are fired by .Net loader and startup time is considerably faster.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: