Я использовал BinaryFormatter для сериализации данных на диск, но он не кажется очень масштабируемым. Я создал файл данных размером 200 МБ, но не могу прочитать его обратно (конец потока обнаружен до завершения синтаксического анализа). Он пытается десериализоваться в течение 30 минут, а затем прекращает работу. Это довольно приличный четырехъядерный процессор с 8 ГБ оперативной памяти.

Я сериализую довольно большую сложную структуру.

HtCacheItems - это хеш-таблица CacheItems. Каждый CacheItem имеет несколько простых членов (строки + целые числа и т. Д.), А также содержит Hashtable и настраиваемую реализацию связанного списка. Под-хеш-таблица указывает на структуры CacheItemValue, которые в настоящее время являются простым DTO, содержащим ключ и значение. Элементы связанного списка также просты.

Файл данных, в котором произошел сбой, содержит около 400 000 CacheItemValues.

Меньшие наборы данных работают хорошо (хотя десериализация и использование чертовски много памяти занимает больше времени, чем я ожидал).

    public virtual bool Save(String sBinaryFile)
    {
        bool bSuccess = false;
        FileStream fs = new FileStream(sBinaryFile, FileMode.Create);

        try
        {
            BinaryFormatter formatter = new BinaryFormatter();
            formatter.Serialize(fs, htCacheItems);
            bSuccess = true;
        }
        catch (Exception e)
        {
            bSuccess = false;
        }
        finally
        {
            fs.Close();
        }
        return bSuccess;
    }

    public virtual bool Load(String sBinaryFile)
    {
        bool bSuccess = false;

        FileStream fs = null;
        GZipStream gzfs = null;

        try
        {
            fs = new FileStream(sBinaryFile, FileMode.OpenOrCreate);

            if (sBinaryFile.EndsWith("gz"))
            {
                gzfs = new GZipStream(fs, CompressionMode.Decompress);
            }

            //add the event handler
            ResolveEventHandler resolveEventHandler = new ResolveEventHandler(AssemblyResolveEventHandler);
            AppDomain.CurrentDomain.AssemblyResolve += resolveEventHandler;

            BinaryFormatter formatter = new BinaryFormatter();
            htCacheItems = (Hashtable)formatter.Deserialize(gzfs != null ? (Stream)gzfs : (Stream)fs);

            //remove the event handler
            AppDomain.CurrentDomain.AssemblyResolve -= resolveEventHandler;

            bSuccess = true;
        }
        catch (Exception e)
        {
            Logger.Write(new ExceptionLogEntry("Failed to populate cache from file " + sBinaryFile + ". Message is " + e.Message));
            bSuccess = false;
        }
        finally
        {
            if (fs != null)
            {
                fs.Close();
            }
            if (gzfs != null)
            {
                gzfs.Close();
            }
        }
        return bSuccess;
    }

ResolutionEventHandler - это просто обходной путь, потому что я сериализую данные в одном приложении и загружаю их в другое ( http://social.msdn.microsoft.com/Forums/en-US/netfxbcl/thread/e5f0c371-b900-41d8-9a5b-1052739f2521 )

Вопрос в том, как я могу это улучшить? Всегда ли сериализация данных будет неэффективной, лучше ли мне писать свои собственные процедуры?

3
Gordon Thompson 17 Июл 2009 в 12:43
Добавлен пример, который пытается имитировать ваше описание.
 – 
Marc Gravell♦
17 Июл 2009 в 15:00
(исправлена ​​проблема со связанным списком; r263. Обратите внимание, что я не перевыпускал двоичный файл; если вы цените это, добавьте комментарий к моему сообщению или напишите мне по электронной почте (см. профиль))
 – 
Marc Gravell♦
19 Июл 2009 в 02:22

3 ответа

Лучший ответ

Я бы лично попытался избежать необходимости в сборке-разрешении; это имеет определенный запах. Если вам необходимо использовать BinaryFormatter, я бы просто поместил DTO в отдельную библиотеку (dll), которую можно было бы использовать в обоих приложениях.

Если вы не хотите делиться библиотекой, то IMO вам не следует использовать BinaryFormatter - вы должны использовать сериализатор на основе контракта, такой как XmlSerializer или DataContractSerializer, или одна из реализаций "протокольных буферов" (и, повторяя заявление Джона, я написал одну из других).

200 МБ кажутся довольно большими, но я не ожидал, что это не удастся. Одна из возможных причин здесь - отслеживание объекта для ссылок; но даже тогда это меня удивляет.

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


Вот пример, который пытается отразить вашу настройку из описания с помощью protobuf-net. Как ни странно, похоже, есть сбой при работе со связанным списком, который я исследую; но остальное вроде работает:

using System;
using System.Collections.Generic;
using System.IO;
using ProtoBuf;
[ProtoContract]
class CacheItem
{
    [ProtoMember(1)]
    public int Id { get; set; }
    [ProtoMember(2)]
    public int AnotherNumber { get; set; }
    private readonly Dictionary<string, CacheItemValue> data
        = new Dictionary<string,CacheItemValue>();
    [ProtoMember(3)]
    public Dictionary<string, CacheItemValue> Data { get { return data; } }

    //[ProtoMember(4)] // commented out while I investigate...
    public ListNode Nodes { get; set; }
}
[ProtoContract]
class ListNode // I'd probably expose this as a simple list, though
{
    [ProtoMember(1)]
    public double Head { get; set; }
    [ProtoMember(2)]
    public ListNode Tail { get; set; }
}
[ProtoContract]
class CacheItemValue
{
    [ProtoMember(1)]
    public string Key { get; set; }
    [ProtoMember(2)]
    public float Value { get; set; }
}
static class Program
{
    static void Main()
    {
        // invent 400k CacheItemValue records
        Dictionary<string, CacheItem> htCacheItems = new Dictionary<string, CacheItem>();
        Random rand = new Random(123456);
        for (int i = 0; i < 400; i++)
        {
            string key;
            CacheItem ci = new CacheItem {
                Id = rand.Next(10000),
                AnotherNumber = rand.Next(10000)
            };
            while (htCacheItems.ContainsKey(key = rand.NextString())) {}
            htCacheItems.Add(key, ci);
            for (int j = 0; j < 1000; j++)
            {
                while (ci.Data.ContainsKey(key = rand.NextString())) { }
                ci.Data.Add(key,
                    new CacheItemValue {
                        Key = key,
                        Value = (float)rand.NextDouble()
                    });
                int tail = rand.Next(1, 50);
                ListNode node = null;
                while (tail-- > 0)
                {
                    node = new ListNode
                    {
                        Tail = node,
                        Head = rand.NextDouble()
                    };
                }
                ci.Nodes = node;
            }
        }
        Console.WriteLine(GetChecksum(htCacheItems));
        using (Stream outfile = File.Create("raw.bin"))
        {
            Serializer.Serialize(outfile, htCacheItems);
        }
        htCacheItems = null;
        using (Stream inFile = File.OpenRead("raw.bin"))
        {
            htCacheItems = Serializer.Deserialize<Dictionary<string, CacheItem>>(inFile);
        }
        Console.WriteLine(GetChecksum(htCacheItems));
    }
    static int GetChecksum(Dictionary<string, CacheItem> data)
    {
        int chk = data.Count;
        foreach (var item in data)
        {
            chk += item.Key.GetHashCode()
                + item.Value.AnotherNumber + item.Value.Id;
            foreach (var subItem in item.Value.Data.Values)
            {
                chk += subItem.Key.GetHashCode()
                    + subItem.Value.GetHashCode();
            }
        }
        return chk;
    }
    static string NextString(this Random random)
    {
        const string alphabet = "abcdefghijklmnopqrstuvwxyz0123456789 ";
        int len = random.Next(4, 10);
        char[] buffer = new char[len];
        for (int i = 0; i < len; i++)
        {
            buffer[i] = alphabet[random.Next(0, alphabet.Length)];
        }
        return new string(buffer);
    }
}
2
Marc Gravell 17 Июл 2009 в 15:00
Я расширил детали, чтобы дать обзор структур, которые я хочу сериализовать. Благодарность
 – 
Gordon Thompson
17 Июл 2009 в 13:40

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

Обычно существует компромисс между переносимостью и гибкостью того, что вы можете сериализовать. Например, вы можете захотеть использовать протоколы буферов (отказ от ответственности: я написал один из портов C #) как довольно эффективное решение с хорошей переносимостью и управлением версиями - но тогда вам нужно будет преобразовать вашу естественную структуру данных во что-то, поддерживаемое буферами протокола.

Сказав это, я удивлен, что двоичная сериализация здесь терпит неудачу - по крайней мере, в этом конкретном случае. Можете ли вы заставить его выйти из строя с большим файлом с очень и очень простым фрагментом кода сериализации? (Без обработчиков разрешения, без сжатия и т. Д.)

2
Jon Skeet 17 Июл 2009 в 12:51
В этом случае файл не сжат, это было то, что я вставил, чтобы ускорить загрузку. Я не могу легко отключить обработчики разрешения, потому что данные были сгенерированы отдельной утилитой (выполнение которой заняло около 8 часов). Я посмотрю на буферы протоколов и посмотрю, поможет ли это. Благодарность
 – 
Gordon Thompson
17 Июл 2009 в 13:03

Что-то, что может помочь, - это каскадная сериализация.

Вы вызываете mainHashtable.serialize (), который, например, возвращает строку XML. Этот метод вызывает everyItemInYourHashtable.serialize () и так далее.

Вы делаете то же самое со статическим методом в каждом классе, называемым unserialize (String xml), который десериализует ваши объекты и возвращает объект или список объектов. Вы поняли?

Конечно, вам нужно реализовать этот метод в каждом классе, который вы хотите сериализовать.

Взгляните на интерфейс ISerializable, который представляют именно то, что я описываю. IMO, этот интерфейс выглядит слишком "Microsoft" (без использования DOM и т. Д.), Поэтому я создал свой, но принцип тот же: каскад.

1
Clement Herreman 17 Июл 2009 в 12:51