2009-10-07 12 views
17

Actualmente estoy teniendo un problema realmente extraño y no puedo encontrar la manera de resolverlo.Problema de rendimiento de XmlSerializer al especificar XmlRootAttribute

Tengo un tipo complejo bastante que estoy intentando serializar utilizando la clase XmlSerializer. Esto realmente funciona bien y el tipo serializa correctamente, pero parece tomar un muy largo tiempo al hacerlo; alrededor de 5 segundos dependiendo de los datos en el objeto.

Después de un poco de creación de perfiles, reduje el problema - extrañamente - a la especificación de un XmlRootAttribute cuando llamo a XmlSerializer.Serialize. Hago esto para cambiar el nombre de una colección que se está serializando desde ArrayOf a algo un poco más significativo. Una vez que elimine el parámetro, ¡la operación es casi instantánea!

¡Cualquier idea o sugerencia sería excelente ya que estoy totalmente perplejo con esta!

+1

De acuerdo, parece que el problema es que ¡El ensamblaje de serialización se genera para cada instancia de serializador si usted especifica cualquier cosa que no sea un parámetro de tipo para el serializador! Por eso, supongo, estoy viendo un rendimiento tan terrible. ¿Alguien sabe alguna razón por la cual XmlSerializer predeterminado haría esto? No entiendo por qué simplemente especificar el nombre del nodo raíz significaría que la memoria caché no podría ser utilizada? – Dougc

Respuesta

23

Sólo por cualquier otra persona que se encuentra con este problema; armado con la respuesta anterior y el ejemplo de MSDN logré resolver este problema utilizando la clase siguiente:

public static class XmlSerializerCache 
{ 
    private static readonly Dictionary<string, XmlSerializer> cache = 
          new Dictionary<string, XmlSerializer>(); 

    public static XmlSerializer Create(Type type, XmlRootAttribute root) 
    { 
     var key = String.Format(
        CultureInfo.InvariantCulture, 
        "{0}:{1}", 
        type, 
        root.ElementName); 

     if (!cache.ContainsKey(key)) 
     { 
      cache.Add(key, new XmlSerializer(type, root)); 
     } 

     return cache[key]; 
    } 
} 

A continuación, en lugar de utilizar el constructor XmlSerializer por defecto que toma un XmlRootAttribute, utilizo el siguiente lugar:

var xmlRootAttribute = new XmlRootAttribute("ExampleElement"); 
var serializer = XmlSerializerCache.Create(target.GetType(), xmlRootAttribute); 

¡Mi aplicación ahora se está realizando otra vez!

+0

+1, corto y dulce. –

+6

Una ligera y ligera optimización al respecto. Intenta obtener el valor en la cláusula if, si vas a hacer muchas de estas búsquedas. ContainsKey es más eficiente si solo quiere saber si el artículo existe, pero no lo obtiene, pero como siempre devuelve el valor, tryget es mejor: http://dotnetperls.com/dictionary-lookup –

+0

Luego también puede mejorar " return "statement, ya que ya tiene la instancia en la variable y no necesita buscar a través de" cache [key] " – Sielu

18

Como se ha mencionado en el comentario de seguimiento a la pregunta original, .NET emite asambleas al crear XmlSerializers, y almacena en caché el conjunto generado si se ha creado usando uno de estos dos constructores:

XmlSerializer(Type) 
XmlSerializer(Type, String) 

asambleas generados el uso de los otros constructores no se almacena en caché, por lo que .NET debe generar ensamblados nuevos cada vez.

¿Por qué? Probablemente esta respuesta no sea muy satisfactoria, pero al observar esto en Reflector, puede ver que la clave utilizada para almacenar y acceder a los ensamblados XmlSerializer generados (TempAssemblyCacheKey) es simplemente una clave compuesta compuesta del tipo serializable y (opcionalmente) su espacio de nombres

Por lo tanto, no hay un mecanismo que indique si un XmlSerializer en caché para SomeType tiene un XmlRootAttribute especial o el predeterminado.

Es difícil pensar en una razón técnica por la que la clave no pueda acomodar más elementos, por lo que esta es probablemente una característica que nadie tuvo tiempo de implementar (especialmente porque implicaría cambiar clases por lo demás estables).

Es posible que haya visto esto, pero en caso de que no tienen, the XmlSerializer class documentation discute una solución:

Si utiliza cualquiera de los otros constructores , varias versiones del mismo conjunto se generan y nunca descargado, lo que da como resultado una pérdida de memoria y un bajo rendimiento. La solución más fácil de es usar uno de los anteriormente mencionados dos constructores. De lo contrario, debe almacenar en caché los ensamblajes en un Hashtable, como se muestra en en el siguiente ejemplo.

(he omitido el ejemplo aquí)

+0

Excelente. Gracias por su ayuda Jeff. No tengo idea de por qué no leí la documentación de MSDN, doh! – Dougc

+0

¿Hay algún ejemplo de este HashTable que se utiliza donde no tienes control sobre el Serializador Xml? Estoy pensando específicamente en una referencia de servicio WCF o referencia web? Tengo este problema, pero parece que no puedo encontrar ninguna manera de solucionarlo ...! – harrisonmeister

0

Hay una implementación más compleja explicada here. Sin embargo, el proyecto ya no está activo.

Las clases pertinentes son visibles aquí: http://mvpxml.codeplex.com/SourceControl/changeset/view/64156#258382

En particular, la siguiente función para generar una clave única puede ser útil:

public static string MakeKey(Type type 
    , XmlAttributeOverrides overrides 
    , Type[] types 
    , XmlRootAttribute root 
    , String defaultNamespace) { 
    StringBuilder keyBuilder = new StringBuilder(); 
    keyBuilder.Append(type.FullName); 
    keyBuilder.Append("??"); 
    keyBuilder.Append(SignatureExtractor.GetOverridesSignature(overrides)); 
    keyBuilder.Append("??"); 
    keyBuilder.Append(SignatureExtractor.GetTypeArraySignature(types)); 
    keyBuilder.Append("??"); 
    keyBuilder.Append(SignatureExtractor.GetXmlRootSignature(root)); 
    keyBuilder.Append("??"); 
    keyBuilder.Append(SignatureExtractor.GetDefaultNamespaceSignature(defaultNamespace)); 

    return keyBuilder.ToString(); 
} 
1

sólo tenía que implementar algo como esto y utilizaron un poco versión más optimizada de la solución de @ Dougc con una sobrecarga de conveniencia:

public static class XmlSerializerCache { 
    private static readonly Dictionary<string, XmlSerializer> cache = new Dictionary<string, XmlSerializer>(); 

    public static XmlSerializer Get(Type type, XmlRootAttribute root) { 
     var key = String.Format("{0}:{1}", type, root.ElementName); 
     XmlSerializer ser; 
     if (!cache.TryGetValue(key, out ser)) { 
      ser = new XmlSerializer(type, root); 
      cache.Add(key, ser); 
     } 
     return ser; 
    } 

    public static XmlSerializer Get(Type type, string root) { 
     return Get(type, new XmlRootAttribute(root)); 
    } 
} 
Cuestiones relacionadas