Comparaison de performance des sérialiseur XML .net (DataContractSerializer, XmlSerializer, SoapFormatter)
Il est fréquent que l’on veuille transformer un objet .net dans un format XML afin de le sauvegarder sur le disque dur, en base, etc. Cette opération correspond à la sérialisation XML.
Un sérialiseur est une classe .net permettant de faire cette opération de sérialisation. A partir de .net 3.5 SP1, .net fourni 3 sérialiseurs différents : XmlSerializer, SoapFormatter, DataContractSerializer.
Ces 3 sérialiseurs ont chacun leurs avantages et inconvénients. J’ai décidé aujourd’hui de m’intéresser aux performances de ces sérialiseurs. Les performances que je mesure ici sont le temps d’exécution, je ne mesure pas la consommation mémoire, l’éventuelle parallélisassion, la taille du fichier générée, etc. De plus, je ne prends pas en compte les spécificités de chacun des sérialiseurs, je me situe dans un cas d’utilisation très basique.
Environnement de test
Les tests ont été exécutés sur mon laptop (Win 7 enterprise, 8Go de ram, 8 coeur), en mode de compilation release, any CPU avec .net 4.0. Les temps de réponses affichés sont exprimés en millisecondes et correspondent à une moyenne d’au moins 3 exécutions.
J’ai également ajouté une comparaison en utilisant le BinaryFormatter, le sérialiseur qui transforme les données au format binaire.
J’ai effectué mes tests avec 3 types différents :
Type simple :
[Serializable] // required for soapformatter
public class A
{
public int MyProperty { get; set; }
public int MyProperty2 { get; set; }
}
Type intermediaire :
[Serializable] // required for soapformatter
public class B
{
public int MyProperty { get; set; }
public int MyProperty2 { get; set; }
public String MyProperty3 { get; set; }
public DateTime MyProperty4 { get; set; }
public A MyPropertyA1 { get; set; }
public A MyPropertyA2 { get; set; }
}
Type complexe :
[Serializable] // required for soapformatter
public class C
{
public int MyProperty { get; set; }
public int MyProperty2 { get; set; }
public int MyProperty3 { get; set; }
public A MyPropertyA1 { get; set; }
public A MyPropertyA2 { get; set; }
public B MyPropertyB1 { get; set; }
public B MyPropertyB2 { get; set; }
}
A chaque sérialisation, je créé une nouvelle instance de ces objets, le code utilisé est le suivant :
static A GetA()
{
return new A()
{
MyProperty = 1,
MyProperty2 = 2
};
}
static B GetB()
{
return new B()
{
MyProperty = 1,
MyProperty2 = 2,
MyProperty3 = "3",
MyProperty4 = DateTime.Now,
MyPropertyA1 = GetA(),
MyPropertyA2 = GetA()
};
}
static C GetC()
{
return new C()
{
MyProperty = 1,
MyProperty2 = 2,
MyProperty3 = 3,
MyPropertyA1 = GetA(),
MyPropertyA2 = GetA(),
MyPropertyB1 = GetB(),
MyPropertyB2 = GetB()
};
}
Enfin, le code utilisé pour l’exécution des tests est le suivant :
Serialisation :
Stopwatch watcher = Stopwatch.StartNew();
// SoapFormatter serializer = new SoapFormatter();
for (int i = 0; i < 10000; i++)
{
SoapFormatter serializer = new SoapFormatter();
using (MemoryStream ms = new MemoryStream())
{
B b = GetB();
serializer.Serialize(ms, b);
}
if (i == 0)
{
Console.WriteLine(watcher.ElapsedMilliseconds);
}
}
Console.WriteLine(watcher.ElapsedMilliseconds);
Déserialisation :
using (MemoryStream ms = new MemoryStream())
{
var o = GetA();
var innerSerializer = new DataContractSerializer(typeof(A));
innerSerializer.WriteObject(ms, o);
while (true)
{
Stopwatch watcher = Stopwatch.StartNew();
var serializer = new DataContractSerializer(typeof(A));
for (int i = 0; i < 10000; i++)
{
//var serializer = new DataContractSerializer(typeof(A));
using (MemoryStream ms2 = new MemoryStream())
{
ms.Position = 0;
ms.CopyTo(ms2);
ms2.Position = 0;
serializer.ReadObject(ms2);
}
if (i == 0)
{
Console.WriteLine(watcher.ElapsedMilliseconds);
}
}
Console.WriteLine(watcher.ElapsedMilliseconds);
Console.ReadLine();
}
}
Les tests ont été réalisés selon deux modes, soit en instanciant à chaque fois un sérialiseur, soit en mutualisant le sérialiseur pour les 10 000 itérations.
Je différencie également le temps d’exécution de la première sérialisation des 9 999 suivants.
Résultat de test
Les résultats ci-dessous sont exprimés en millisecondes.
Sérialisation
Résultats lorsque l’on instancie un nouveau sérialiseur à chaque itération :

Résultat lorsque l’on mutualise l’instance du sérialiseur pour les 10 000 itérations :

Constat :
La première chose que l’on peut remarquer est qu’à part pour le XmlSerializer, il y a peu de différence entre la réutilisation ou non du sérialiseur. Dans le cas du XmlSerializer, on voit que réutiliser le type permet de meilleure performance. A noter que le XmlSerializer est Thread Safe il peut donc être stockée dans une variable static et mutualiser entre différents threads.
On voit ensuite que le XmlSerializer est le plus long pour des objets simples, dès que les objets deviennent plus complexes, le SoapFormatter devient de plus en plus lent.
Les sérialiseurs les plus rapides sont les BinaryFormatter et DataContractSerializer. Le BinaryFormatter est rapide dès la première exécution alors que le DataContractSerializer est un peu plus lent au démarrage, il devient ensuite plus rapide lorsque l’on effectue plusieurs sérialisations.
Déserialisation
Résultat lorsque l’on instancie un nouveau sérialiseur à chaque itération :

Résultat lorsque l’on mutualise l’instance du sérialiseur pour les 10 000 itérations :

Constat :
Comme lors de la sérialisation, on constate que l’initialisation du XmlSerializer est assez couteux, une fois de plus il est préférable de mutualiser l’instance du XmlSerializer.
Pour les types complexes, à part avec le SoapFormatter, la déserialisation prend à peu près le même temps. Pour les types simples, le BinaryFormatter reste le plus rapide.
Conclusion
Ces résultats nous amène aux conclusions suivantes :
- Pour la déserialisation, le BinaryFormatter est le plus rapide suivi de très près par la DataContractSerializer
- Pour la sérialisation,
- pour des types complexe, le DataContractSerializer est le plus rapide
- pour des types simple, le BinaryFormatter est le plus rapide
- Si l’on utilise le XmlSerializer, il est préférable de conserver l’instance dans une variable static.
- Le SoapFormatter est le serialiseur le plus lent
Attention, il ne faut pas oublier que chaque sérialiseur a ses propres spécificités. Lors du choix d’un sérialiseur, la performance n’est pas le seul critère à prendre en compte. De plus, je ne mesure pas le temps d’exécution lorsque l’on utilise ces spécificités.
Et vous, avez-vous constaté des différences de performance entre les différents sérialiseur ?