Подтвердить что ты не робот

СериализацияException при сериализации множества объектов в .NET.

У меня возникают проблемы с сериализацией множества объектов в .NET. Граф объекта довольно большой, и некоторые из новых наборов данных используются, поэтому я получаю:

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

Кто-нибудь еще попал в этот предел? Как вы его решили?

Было бы неплохо, если бы я по-прежнему мог использовать встроенный механизм сериализации, но, похоже, нужно просто сворачивать мои собственные (и поддерживать обратную совместимость с существующими файлами данных)

Все объекты POCO и сериализуются с использованием BinaryFormatter. Каждый сериализуемый объект реализует ISerializable, чтобы выборочно сериализовать его элементы (некоторые из них пересчитываются во время загрузки).

Похоже, что это открытая проблема для MS (подробности здесь), но она была решена как Wont Fix. Подробности (со ссылкой):

Двоичная сериализация не выполняется для объекта графики с более чем 13,2 миллионами объекты. Попытка сделать это вызывает исключение в ObjectIDGenerator.Rehash с вводящее в заблуждение сообщение об ошибке Int32.MaxValue.

После изучения ObjectIDGenerator.cs в SSCLI Исходный код, кажется, что больше Графы объектов могут обрабатываться добавление дополнительных записей в размер массива. См. Следующие строки:

// 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};

Однако было бы неплохо, если бы сериализация разумный размер графа объекта.

4b9b3361

Ответ 1

Я попытался воспроизвести проблему, но код просто навсегда запускается, даже если каждый из 13+ миллионов объектов имеет всего 2 байта. Поэтому я подозреваю, что вы можете не только исправить эту проблему, но и значительно повысить производительность, если вы упакуете свои данные немного лучше в своих пользовательских реализациях ISerialize. Не позволяйте сериализатору видеть так глубоко в вашей структуре, но отключите его в том месте, где ваш графический объект разбивается на сотни тысяч элементов массива или больше (потому что, предположительно, если у вас есть много объектов, они довольно маленькие или вы не сможете держать их в памяти в любом случае). Возьмем этот пример, который позволяет сериализатору видеть классы B и C, но вручную управляет коллекцией класса 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));
        }
    }
}

Ответ 2

Задумывались ли вы о том, что Int32.MaxValue составляет 2,147,483,647 - более 2 миллиардов.

Вам понадобится 16 ГБ памяти, чтобы хранить указатели (при условии 64-битной машины), не говоря уже о самих объектах. Половина этого на 32-битной машине, хотя сжимает 8 Гбайт данных указателя в максимально 3 ГБ или около того, полезное пространство будет хорошим трюком.

Я сильно подозреваю, что ваша проблема заключается не в количестве объектов, а в том, что структура сериализации входит в какой-то бесконечный цикл, потому что у вас есть ссылочные петли в вашей структуре данных.

Рассмотрим этот простой класс:

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

Этот простой класс не может быть сериализован, поскольку наличие свойства Parent означает, что сериализация перейдет в бесконечный цикл.

Поскольку вы уже внедряете ISerializable, вы получаете 75% от этого решения - вам просто нужно убедиться, что вы удаляете любые циклы из графика объекта, который вы храните, вместо этого, чтобы сохранить дерево объектов.

Одним из методов, который часто используется, является сохранение имени (или идентификатора) ссылочного объекта вместо фактической ссылки, разрешение имени обратно на объект при загрузке.

Ответ 3

В зависимости от структуры данных, возможно, вы можете сериализовать/десериализовать подграфы вашего графа больших объектов? Если данные могут быть разделены каким-то образом, вы можете уйти от него, создав лишь небольшое дублирование сериализованных данных.

Ответ 4

Я предполагаю... сериализуйте меньше объектов за раз?

2 основных вопроса:

  • какие объекты они?
    • ПОКО?
    • DataTable?
  • какой тип сериализации?
    • XML?
      • XmlSerializer?
      • DataContractSerializer?
    • двоичная?
      • BinaryFormatter?
      • SoapFormatter?
    • другой?
      • JSON?
      • сделанный на заказ?

Сериализация должна иметь некоторое представление о том, что такое объем данных; например, некоторые структуры сериализации поддерживают потоковую передачу как объектов, так и сериализованных данных, а не полагаются на полный граф объектов или временное хранилище.

Другой вариант - сериализовать однородные наборы данных, а не полные графики, т.е. сериализовать всех "заказчиков" отдельно "заказы"; это обычно уменьшает объемы, за счет большей сложности.

Итак: каков сценарий здесь?

Ответ 5

Похоже, вы столкнулись с внутренним ограничением в рамках. Вы можете написать свою собственную сериализацию, используя BinaryReader/Writer или DataContractSerializer или что-то еще, но это не идеально, я знаю.

Ответ 6

Чувак, ты дошел до конца .net!

Я не попал в этот предел, но вот несколько указателей:

  • используйте [XmlIgnore], чтобы пропустить некоторые из объектов - возможно, вам не нужно сериализовывать все

  • вы можете использовать сериализатор вручную (т.е. не с атрибутами, а путем реализации Serialize()) и разбивать модели на большее количество файлов.

Ответ 7

Вам нужно получить все данные одновременно? Тринадцать миллионов объектов - это много информации для обработки сразу.

Вы можете реализовать механизм поискового вызова и получить данные в меньших частях. И это может увеличить оперативность приложения, так как вам не придется ждать завершения всех этих объектов.