2009-07-17 7 views
3

J'ai utilisé BinaryFormatter pour sérialiser des données sur le disque mais cela ne semble pas très évolutif. J'ai créé un fichier de données de 200 Mo mais je suis incapable de le relire (Fin du flux rencontré avant l'achèvement de l'analyse). Il essaie pendant environ 30 minutes de se désérialiser puis abandonne. C'est sur une boîte quad-cpu assez décente avec 8 Go de RAM.C# données sérialisées

Je suis en train de sérialiser une structure assez complexe.

htCacheItems est une Hashtable de CacheItems. Chaque CacheItem a plusieurs membres simples (chaînes + ints etc) et contient également une Hashtable et une implémentation personnalisée d'une liste chaînée. La sous-table de hachage pointe vers les structures CacheItemValue qui est actuellement un simple DTO qui contient une clé et une valeur. Les éléments de la liste liée sont également simples.

Le fichier de données qui échoue contient environ 400 000 CacheItemValues.

De plus petits ensembles de données fonctionnent bien (bien que cela prenne plus de temps que je m'attendrais à désérialiser et utiliser beaucoup de mémoire).

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

Le resolveEventHandler est juste un travail autour parce que je suis sérialisation les données dans une seule application et le charger dans un autre (http://social.msdn.microsoft.com/Forums/en-US/netfxbcl/thread/e5f0c371-b900-41d8-9a5b-1052739f2521)

La question est, comment puis-je améliorer cela? La sérialisation des données va toujours être inefficace, est-ce que je préfère écrire mes propres routines?

+0

Ajout d'un exemple qui essaie d'imiter votre description. –

+0

(corrigé le problème avec la liste chaînée; r263.) Note: Je n'ai pas relibéré le binaire, si vous voulez le faire, ajoutez un commentaire à mon message, ou envoyez-moi un e-mail (voir profil) –

Répondre

2

Je tenterais personnellement d'éviter le besoin de l'assemblage-résolution; cela a une certaine odeur à ce sujet. Si vous devez utiliser BinaryFormatter, alors je mettrais simplement les DTO dans une bibliothèque séparée (dll) qui peut être utilisée dans les deux applications.

Si vous ne voulez pas partager la dll, puis l'OMI que vous ne devriez pas utiliser BinaryFormatter - vous devriez utiliser un sérialiseur contractuelle, tels que XmlSerializer ou DataContractSerializer, ou l'une des implémentations « tampons de protocole » (et pour répéter l'avertissement de Jon: j'ai écrit one of the others).

200MB semble assez grand, mais je ne m'attendais pas à ce qu'il échoue. Une cause possible ici est le suivi d'objet qu'il fait pour les références; mais même alors, cela me surprend.

Je serais ravi de voir un modèle d'objet simplifié pour voir si c'est un "ajustement" pour tout ce qui précède. Voici un exemple qui tente de refléter votre configuration à partir de la description en utilisant protobuf-net. Assez curieusement, il semble y avoir un petit problème fonctionnant avec la liste liée, which I'll investigate; mais le reste semble fonctionner:

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 = "abcdefghijklmnopqrstuvwxyz"; 
     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); 
    } 
} 
+0

J'ai développé le détail pour donner un aperçu des structures que je veux sérialiser. Merci –

1

Quelque chose qui pourrait aider est la sérialisation en cascade.

Vous appelez mainHashtable.serialize(), qui renvoie une chaîne XML par exemple. Cette méthode appelle everyItemInYourHashtable.serialize(), et ainsi de suite. Vous faites de même avec une méthode statique dans chaque classe, appelée 'unserialize (String xml)', qui désérialise vos objets et renvoie un objet, ou une liste d'objets. Vous obtenez le point?

Bien sûr, vous devez implémenter cette méthode dans toutes les classes que vous voulez sérialiser.

Jetez un oeil à ISerializable interface, qui représentent exactement ce que je décris. IMO, cette interface ressemble trop à "Microsoft" (pas d'utilisation de DOM, etc), donc j'ai créé le mien, mais le principe est le même: cascade.

+0

merci, je Je vais vérifier –

2

La sérialisation est délicate, en particulier lorsque vous souhaitez avoir une certaine flexibilité en matière de versionnage.

Habituellement, il y a un compromis entre la portabilité et la flexibilité de ce que vous pouvez sérialiser. Par exemple, vous pouvez utiliser Protocol Buffers (disclaimer: j'ai écrit one of the C# ports) comme une solution assez efficace avec une bonne portabilité et une bonne gestion des versions - mais vous devrez ensuite traduire votre structure de données naturelle en quelque chose supporté par les tampons de protocole. Cela dit, je suis surpris que la sérialisation binaire échoue ici - du moins de cette façon particulière. Pouvez-vous faire échouer avec un gros fichier avec un très, très simple code de sérialisation? (Pas de gestionnaires de résolution, pas de compression, etc.)

+0

le fichier n'est pas compressé dans cette instance, c'est quelque chose que j'ai mis en place pour essayer d'accélérer le chargement. Je ne peux pas facilement désactiver les gestionnaires de résolution car les données ont été générées par un utilitaire séparé (qui a pris environ 8 heures pour s'exécuter). Je vais jeter un coup d'oeil aux tampons de protocole et voir si cela aide. merci –