2009-07-17 20 views
3

私はBinaryFormatterを使用してデータをディスクにシリアル化していますが、スケーラビリティはあまり高くありません。 200Mbのデータファイルを作成しましたが、それを読み込めませんでした(解析が完了する前にストリームの終わりが見つかりました)。それは、脱直列化してから放棄するまで約30分間試行します。これは8GbのRAMを搭載したかなりまともなクアッドCPUボックスに搭載されています。c#シリアル化されたデータ

かなり複雑な構造をシリアル化しています。

htCacheItemsはCacheItemsのハッシュテーブルです。各CacheItemにはいくつかの単純なメンバー(文字列+ intなど)があり、ハッシュテーブルとリンクリストのカスタム実装も含まれています。サブハッシュテーブルは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; 
    } 

resolveEventHandlerは、どのように私はこれを改善することができ、私は1つのアプリケーション内のデータを直列化し、別の(http://social.msdn.microsoft.com/Forums/en-US/netfxbcl/thread/e5f0c371-b900-41d8-9a5b-1052739f2521

質問がある中で、それをロードしてる周りのためだけの仕事ですか?データのシリアライゼーションは常に効率的ではないでしょうか?私自身のルーチンを書いたほうがいいですか?

+0

説明を模倣しようとする例を追加しました。 –

+0

(リンクされたリストの問題を修正; r263。)バイナリを再リリースしていないので、これを評価したり、自分の投稿にコメントを追加したり、私に電子メールを送ってください。(プロファイルを参照) –

答えて

2

私は個人的にアセンブリ解決の必要性を避けようとします。それにはある種の匂いがあります。 にする必要がある場合は、BinaryFormatterを使用すると、DTOを両方のアプリケーションで使用できる別のライブラリ(dll)に入れるだけです。

あなたは、IMOあなたがBinaryFormatterを使用すべきではない、DLLを共有したくない場合 - あなたは、このようなXmlSerializerDataContractSerializer、または「プロトコルバッファ」実装の1つとして、契約ベースのシリアライザを使用する必要があります(そして、ジョンの免責事項を繰り返す:私はone of the othersと書いた)。

200MBはかなり大きいようですが、失敗するとは思わないでしょう。ここで考えられる原因の1つに、参照用に追跡するオブジェクトがあります。しかしそれでも、これは私を驚かせます。

私は単純化されたオブジェクトモデルを見て、それが上記のいずれにも「適合」しているかどうかを知りたいと思います。


ここでは、protobuf-netを使用した説明から設定をミラーリングする例を示します。奇妙なことにリンクリストを扱うグリッチがあるようです。which I'll investigate;残りはうまくいくようです:

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

私はシリアル化したい構造の概要を与えるために詳細を拡張しました。ありがとう –

1

カスケードのシリアライズに役立つものがあります。

たとえば、XML文字列を返すmainHashtable.serialize()を呼び出します。このメソッドはeveryItemInYourHashtable.serialize()などを呼び出します。

「unserialize(String xml)」と呼ばれるすべてのクラスで静的メソッドを使用してオブジェクトを直列化し、オブジェクトまたはオブジェクトのリストを返します。 あなたはポイントを得る?

もちろん、シリアライズ可能にするすべてのクラスにこのメソッドを実装する必要があります。

ISerializable interfaceを見てください。これは私が説明していることを表しています。 IMOは、このインターフェイスはあまりにも "マイクロソフト"(DOMの使用などはない)ので、私は私を作成しましたが、原則は同じです:カスケード。

+0

それをチェックしてみてください –

2

特に、バージョン管理に関して柔軟性を持たせたい場合は、シリアル化が難しいです。

通常、シリアル化できるものの移植性と柔軟性にはトレードオフがあります。たとえば、Protocol Buffers(免責事項:私はone of the C# portsと書いておきます)は、移植性とバージョニング性に優れた非常に効率的なソリューションですが、あなたの自然なデータ構造が何であれプロトコルバッファでサポートされているものに変換する必要があります。

私は、バイナリシリアル化がここでは失敗していることに驚いています - 少なくともその特定の方法では。非常にシンプルなシリアライゼーションコードを使用して大きなファイルで失敗することはありますか? (解像度ハンドラなし、圧縮なしなど)

+0

ファイルがこのインスタンスで圧縮されていない、それは私が試してロードを高速化するために入れたものでした。私はデータが別々のユーティリティ(実行に約8時間かかった)によって生成されたため、解決ハンドラを簡単に無効にすることはできません。私はプロトコルのバッファを見て、それが役立つかどうかを見ていきます。ありがとう –

関連する問題