2009-07-17 2 views
3

BinaryFormatter를 사용하여 디스크에 데이터를 serialize했지만 매우 확장 성이 좋지 않습니다. 200MB의 데이터 파일을 만들었지 만 다시 읽을 수 없습니다 (분석이 끝나기 전에 발생한 스트림의 끝). 그것은 deserialise에 약 30 분을 시도한 다음 포기합니다. 이것은 8Gb RAM이있는 상당히 괜찮은 쿼드 - CPU 상자에 있습니다.C# serialize 된 데이터

상당히 복잡한 구조를 직렬화하고 있습니다.

htCacheItems는 CacheItems의 해시 테이블입니다. 각 CacheItem에는 여러 간단한 멤버 (문자열 + int 등)가 있으며 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; 
    } 

resolveEventHandler는 어떻게이 문제를 개선 할 수 있습니다, 내가 한 응용 프로그램에서 데이터를 serialising 다른 (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을 공유하지 않으려면 - 당신은 같은 XmlSerializer 또는 DataContractSerializer, 또는 "프로토콜 버퍼"구현 중 하나를 계약 기반의 시리얼 라이저를 사용해야합니다 (그리고 Jon의 면책 조항을 반복하기 위해서 : 나는 one of the others를 썼다).

200MB는 꽤 큰 것처럼 보이지만 실패하지는 않을 것입니다. 한 가지 가능한 원인은 참조에 대해 수행하는 개체 추적입니다. 그러나 그때조차도, 이것은 나를 놀라게한다.

단순화 된 개체 모델을보고 위의 항목 중 "적합"한지 확인하고 싶습니다.


다음은 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

나는 serialize하려는 구조의 개요를 제공하기 위해 세부 사항을 확장했습니다. 감사 –

1

계단식 직렬화가 도움이 될 수 있습니다.

예를 들어 XML 문자열을 반환하는 mainHashtable.serialize()를 호출합니다. 이 메소드는 everyItemInYourHashtable.serialize() 등을 호출합니다.

'unserialize (String xml)'라고하는 모든 클래스에서 정적 메서드를 사용하여 동일한 작업을 수행하면 개체를 직렬화하고 개체 또는 개체 목록을 반환합니다. 요점은 무엇입니까?

물론 직렬화 할 모든 클래스에이 메서드를 구현해야합니다.

ISerializable interface을 살펴보십시오. 이는 내가 설명하는 것을 표현합니다. IMO,이 인터페이스는 너무 "Microsoft"(DOM 등 사용하지 않음)로 보이기 때문에 광산을 만들었지 만 원리는 동일합니다 : 캐스케이드.

+0

감사합니다. 그것을 확인해 보자. –

2

특히 버전을 지정할 때 어느 정도의 유연성을 원할 때 직렬화가 까다 롭습니다.

일반적으로 직렬화 할 수있는 것의 이식성과 유연성에는 트레이드 오프가 있습니다. 예를 들어, Protocol Buffers (면책 조항 : 나는 one of the C# ports이라고 썼다.)은 이식성과 버전이 좋은 꽤 효율적인 솔루션이다.하지만 자연스러운 데이터 구조가 무엇이든간에 프로토콜 버퍼가 지원하는 것으로 번역해야한다.

그런데, 적어도 바이너리 직렬화가 실패하고 있다는 것에 놀랐습니다. 매우 간단한 코드로 큰 파일을 만들 수 있습니까? (해상도 핸들러 없음, 압축 없음 등)

+0

파일은이 인스턴스에 압축되어 있지 않다. 데이터가 별도의 유틸리티 (8 시간 정도 소요)에서 생성 되었기 때문에 해결 핸들러를 쉽게 끌 수 없습니다. 프로토콜 버퍼를 살펴보고 도움이되는지 확인하겠습니다. 덕분에 –

관련 문제