Исключение SerializationException при сериализации большого количества объектов в .NET - PullRequest
20 голосов
/ 20 февраля 2009

У меня проблемы с сериализацией большого количества объектов в .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};

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

Ответы [ 8 ]

10 голосов
/ 09 апреля 2009

Я попытался воспроизвести проблему, но запуск кода занимает вечность, даже если каждый из 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));
        }
    }
}
1 голос
/ 19 ноября 2018

Эта проблема была исправлена ​​в .NET Core 2.1. Я попросил перенести решение в .NET Framework 4.8:

https://github.com/Microsoft/dotnet-framework-early-access/issues/46.

Если вы считаете, что проблема должна быть решена, вы можете оставить комментарий, что это также важно для вас. Исправление в .NET Core заключалось в том, чтобы повторно использовать генератор простых чисел, присутствующий в словаре, также для BinaryFormatter.

Если у вас слишком много сериализованных объектов и вы не хотите ждать 40 минут, чтобы прочитать их обратно, убедитесь, что вы добавили в свой App.Config это:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <runtime>
    <!-- Use this switch to make BinaryFormatter fast with large object graphs starting with .NET 4.7.2 -->
      <AppContextSwitchOverrides value="Switch.System.Runtime.Serialization.UseNewMaxArraySize=true" />
  </runtime>
</configuration>

, чтобы включить исправление десериализации BinaryFormatter, которое, наконец, появилось в .NET 4.7.2. Более подробную информацию об обеих проблемах можно найти здесь:

https://aloiskraus.wordpress.com/2017/04/23/the-definitive-serialization-performance-guide/

1 голос
/ 08 апреля 2009

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

1 голос
/ 03 апреля 2009

Задумывались ли вы о том, что 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% пути к решению этой проблемы - вам просто нужно убедиться, что вы удалили все циклы из сохраняемого графа объектов, чтобы сохранить объект дерево вместо.

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

0 голосов
/ 06 апреля 2009

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

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

0 голосов
/ 20 февраля 2009

Чувак, ты достиг конца .net!

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

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

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

0 голосов
/ 20 февраля 2009

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

0 голосов
/ 20 февраля 2009

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

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

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...