2009-02-20 14 views
16

Tengo problemas para serializar muchos objetos en .NET. El gráfico de objetos es bastante grande, con algunos de los nuevos conjuntos de datos que se utilizan, por lo que estoy consiguiendo:SerializationException al serializar muchos objetos en .NET

System.Runtime.Serialization.SerializationException 
"The internal array cannot expand to greater than Int32.MaxValue elements." 

Alguien más ha golpeado este límite? ¿Cómo lo has resuelto?

Sería bueno si todavía puedo utilizar el construido en el mecanismo de serialización si es posible, pero parece que acaba de rodar mi propia (y mantener la compatibilidad con los archivos de datos existentes)

Los objetos son todos POCO y se están serializando usando BinaryFormatter. Cada objeto que se serializa implementa ISerializable para serializar selectivamente sus miembros (algunos de ellos se vuelven a calcular durante la carga).

Parece un problema abierto para MS (details here), pero se ha resuelto como Wont Fix. Los detalles son (desde el enlace):

serialización binaria falla por objeto gráficos con más de 13,2 millones de ~ objetos. El intento de hacerlo provoca una excepción en ObjectIDGenerator.Rehash con un mensaje de error engañoso que hace referencia a Int32.MaxValue.

Tras el examen de ObjectIDGenerator.cs en el código fuente SSCLI , parece que más grandes gráficos de objetos podían ser manejados por la adición de entradas adicionales en la matriz tamaños. Ver las siguientes líneas:

// Table of prime numbers to use as hash table sizes. Each entry is the 
// smallest prime number larger than twice the previous entry. 
private static readonly int[] sizes = {5, 11, 29, 47, 97, 197, 397, 
797, 1597, 3203, 6421, 12853, 25717, 51437, 102877, 205759, 
411527, 823117, 1646237, 3292489, 6584983}; 

Sin embargo, sería bueno si serialización trabajó para cualquier tamaño razonable del gráfico de objetos.

Respuesta

9

He intentado reproducir el problema, pero el código solo tarda una eternidad en ejecutarse incluso cuando cada uno de los 13+ millones de objetos tiene solo 2 bytes. Así que sospecho que no solo podría solucionar el problema, sino también mejorar significativamente el rendimiento si empaqueta sus datos un poco mejor en sus implementaciones personalizadas de ISerialize. No permita que el serializador vea tan profundamente en su estructura, pero córtela en el punto donde su gráfico de objetos explota en cientos de miles de elementos de matriz o más (porque presumiblemente si tiene tantos objetos, son bastante pequeños). o no sería capaz de mantenerlos en la memoria de todos modos). Tome este ejemplo, que permite que el serializador vea las clases B y C, pero gestiona manualmente la colección de la clase A:

class Program 
{ 
    static void Main(string[] args) 
    { 
     C c = new C(8, 2000000); 
     System.Runtime.Serialization.Formatters.Binary.BinaryFormatter bf = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter(); 
     System.IO.MemoryStream ms = new System.IO.MemoryStream(); 
     bf.Serialize(ms, c); 
     ms.Seek(0, System.IO.SeekOrigin.Begin); 
     for (int i = 0; i < 3; i++) 
      for (int j = i; j < i + 3; j++) 
       Console.WriteLine("{0}, {1}", c.all[i][j].b1, c.all[i][j].b2); 
     Console.WriteLine("====="); 
     c = null; 
     c = (C)(bf.Deserialize(ms)); 
     for (int i = 0; i < 3; i++) 
      for (int j = i; j < i + 3; j++) 
       Console.WriteLine("{0}, {1}", c.all[i][j].b1, c.all[i][j].b2); 
     Console.WriteLine("====="); 
    } 
} 

class A 
{ 
    byte dataByte1; 
    byte dataByte2; 
    public A(byte b1, byte b2) 
    { 
     dataByte1 = b1; 
     dataByte2 = b2; 
    } 

    public UInt16 GetAllData() 
    { 
     return (UInt16)((dataByte1 << 8) | dataByte2); 
    } 

    public A(UInt16 allData) 
    { 
     dataByte1 = (byte)(allData >> 8); 
     dataByte2 = (byte)(allData & 0xff); 
    } 

    public byte b1 
    { 
     get 
     { 
      return dataByte1; 
     } 
    } 

    public byte b2 
    { 
     get 
     { 
      return dataByte2; 
     } 
    } 
} 

[Serializable()] 
class B : System.Runtime.Serialization.ISerializable 
{ 
    string name; 
    List<A> myList; 

    public B(int size) 
    { 
     myList = new List<A>(size); 

     for (int i = 0; i < size; i++) 
     { 
      myList.Add(new A((byte)(i % 255), (byte)((i + 1) % 255))); 
     } 
     name = "List of " + size.ToString(); 
    } 

    public A this[int index] 
    { 
     get 
     { 
      return myList[index]; 
     } 
    } 

    #region ISerializable Members 

    public void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) 
    { 
     UInt16[] packed = new UInt16[myList.Count]; 
     info.AddValue("name", name); 
     for (int i = 0; i < myList.Count; i++) 
     { 
      packed[i] = myList[i].GetAllData(); 
     } 
     info.AddValue("packedData", packed); 
    } 

    protected B(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) 
    { 
     name = info.GetString("name"); 
     UInt16[] packed = (UInt16[])(info.GetValue("packedData", typeof(UInt16[]))); 
     myList = new List<A>(packed.Length); 
     for (int i = 0; i < packed.Length; i++) 
      myList.Add(new A(packed[i])); 
    } 

    #endregion 
} 

[Serializable()] 
class C 
{ 
    public List<B> all; 
    public C(int count, int size) 
    { 
     all = new List<B>(count); 
     for (int i = 0; i < count; i++) 
     { 
      all.Add(new B(size)); 
     } 
    } 
} 
+0

Embalar los datos parece una muy buena idea. Incluso puedo usar un MemoryStream para hacer el embalaje, por lo que no es necesario cambiar gran parte del código (puede seguir guardando el modo actual). Y tal vez solo para que las clases "populares" obtengan la cantidad de objetos guardados en un número razonable. – Wilka

0

Supongo ... serializar menos objetos a la vez?

2 preguntas principales:

  • qué objetos son?
    • POCO?
    • DataTable?
  • ¿qué tipo de serialización es?
    • xml?
      • XmlSerializer?
      • DataContractSerializer?
    • binary?
      • BinaryFormatter?
      • SoapFormatter?
    • otro?
      • json?
      • a medida?

serialización necesita tener alguna consideración de lo que es el volumen de datos; por ejemplo, algunos marcos de serialización admiten la transmisión de los objetos y los datos serializados, en lugar de depender de un gráfico de objetos completo o de almacenamiento temporal.

Otra opción es serializar conjuntos de datos homogéneos en lugar de gráficos completos, es decir, serializar separadamente todos los "clientes" los "pedidos"; esto generalmente reduciría los volúmenes, a expensas de tener más complejidad.

Entonces, ¿cuál es el escenario aquí?

+0

He actualizado la pregunta para (con suerte) cubrir sus 2 preguntas. – Wilka

0

¡Amigo, ha llegado al final de .net!

no he golpeado a este límite, pero aquí están algunas sugerencias:

  1. uso [XmlIgnore] omitir algunos de los objetos - tal vez usted no necesita serializar todo

  2. puede usar el serializador manualmente (es decir, no con atributos, sino implementando Serialize()) y particionar los modelos en más archivos.

+0

XmlIgnore solo funciona para la serialización XML. –

1

¿Ha pensado en el hecho de que es Int32.MaxValue 2147483647 - más de 2000 millones.

Necesitaría 16 GB de memoria solo para almacenar los punteros (asumiendo una máquina de 64 bits), sin mencionar los objetos en sí. La mitad de eso en una máquina de 32 bits, aunque exprimir 8GB de datos de puntero en un máximo de 3GB de espacio utilizable sería un buen truco.

Sospecho fuertemente que su problema no es el número de objetos, sino que el marco de serialización está entrando en un tipo de bucle infinito porque tiene bucles de referencia en su estructura de datos.

considerar esta clase simple:

public class Node 
{ 
    public string Name {get; set;} 
    public IList<Node> Children {get;} 
    public Node Parent {get; set;} 
    ... 
} 

Este simple clase no se puede serializar, debido a la presencia de la propiedad Parent significa que la serialización entra en un bucle infinito.

Puesto que ya está implementando ISerializable, que son el 75% de la manera de resolver esto - sólo tiene que asegurarse de quitar todos los ciclos del gráfico de objetos que está almacenando, para almacenar un objeto árbol en su lugar.

Una técnica que se utiliza a menudo es almacenar el nombre (o Identificación del) de un objeto de referencia en lugar de la referencia real, resolver el nombre de nuevo al objeto de la carga.

+2

La pregunta indica BinaryFormatter; que maneja las referencias/recursión correctamente. –

+1

El mensaje Int32.MaxValue es engañoso, añadiré un poco más de detalles a mi pregunta para ampliarlo. – Wilka

0

¿Necesita buscar todos los datos al mismo tiempo? Trece millones de objetos es una gran cantidad de información para manejar a la vez.

Puede implementar un mecanismo de búsqueda y obtener los datos en trozos más pequeños. Y podría aumentar la capacidad de respuesta de la aplicación, ya que no tendría que esperar a que todos esos objetos finalicen la serialización.

+0

Necesita la mayoría de los datos a la vez (algunos podrían tal vez afeitarse). Es necesario para el análisis estadístico. No me preocupa el uso de la memoria de esta parte del programa (se ejecuta en 64 bits con una cantidad decente de ram). Intercambiar cosas en un disco podría hacer que el análisis sea muy lento. – Wilka

1

Dependiendo de la estructura de los datos, ¿tal vez puede serializar/deserializar subgrafos de su gran gráfico de objetos? Si los datos pudieran dividirse de algún modo, podría salirse con la suya, creando solo una pequeña duplicación de datos serializados.

Cuestiones relacionadas