2012-07-03 4 views
8

우리의 응용 프로그램에는 무엇보다도 청크 분할 된 바이트 목록 (현재는 List<byte[]>으로 표시됨)이 들어있는 데이터 구조가 있습니다. 바이트 배열을 대형 객체 힙에 넣을 수있게되면 시간이 지남에 따라 메모리 조각화가 발생하기 때문에 바이트를 채 웁니다.메모리 사용량이 Protobuf-net을 사용하여 청크 바이트 배열을 직렬화합니다.

또한 생성 된 직렬화 DLL을 사용하여 Protobuf-net을 사용하여 이러한 구조를 직렬화하기 시작했습니다.

그러나 우리는 직렬화하는 동안 Protobuf-net이 매우 큰 인 메모리 버퍼를 생성하고 있음을 확인했습니다. 소스 코드를 살펴보면 버퍼의 전체 길이를 나중에 버퍼의 앞쪽에 써야하기 때문에 전체 내부 버퍼가 플러시되지 않을 수도 있습니다 (전체 List<byte[]> 구조가 작성된 것 같습니다).

이것은 불행하게도 바이트를 chunking하는 작업을 취소하고 결국 메모리 조각화로 인해 OutOfMemoryExceptions를줍니다 (예외는 Protobuf-net이 버퍼를 84k 이상으로 확장하려고 시도 할 때 발생합니다. LOH에 저장하고 우리의 전반적인 프로세스 메모리 사용량은 상당히 적음).

Protobuf-net의 작동 방식에 대한 분석이 정확하다면이 문제를 해결할 수있는 방법이 있습니까?


업데이트

마크의 답변에 따라, 여기에 내가 무엇을 시도했다입니다 :

[ProtoContract] 
[ProtoInclude(1, typeof(A), DataFormat = DataFormat.Group)] 
public class ABase 
{ 
} 

[ProtoContract] 
public class A : ABase 
{ 
    [ProtoMember(1, DataFormat = DataFormat.Group)] 
    public B B 
    { 
     get; 
     set; 
    } 
} 

[ProtoContract] 
public class B 
{ 
    [ProtoMember(1, DataFormat = DataFormat.Group)] 
    public List<byte[]> Data 
    { 
     get; 
     set; 
    } 
} 

그런 다음 그것을 직렬화 그러나

var a = new A(); 
var b = new B(); 
a.B = b; 
b.Data = new List<byte[]> 
{ 
    Enumerable.Range(0, 1999).Select(v => (byte)v).ToArray(), 
    Enumerable.Range(2000, 3999).Select(v => (byte)v).ToArray(), 
}; 

var stream = new MemoryStream(); 
Serializer.Serialize(stream, a); 

을 내가 스틱 경우 중단 점이 ProtoWriter.WriteBytes() 인 경우라고합니다.메서드의 맨 아래로 DemandSpace()으로 들어가면 writer.flushLock1이므로 버퍼가 플러시되지 않는 것을 알 수 있습니다.

이 같은베이스상의 또 다른 기본 클래스 작성하는 경우 :

[ProtoContract] 
[ProtoInclude(1, typeof(ABase), DataFormat = DataFormat.Group)] 
public class ABaseBase 
{ 
} 

[ProtoContract] 
[ProtoInclude(1, typeof(A), DataFormat = DataFormat.Group)] 
public class ABase : ABaseBase 
{ 
} 

그런 다음 writer.flushLockDemandSpace()에서 2 동일합니다.

파생 된 유형과 관련하여 여기에 놓친 분명한 단계가 있다고 생각합니까? (bytes로 매핑)

답변

5
내가 (protobuf 말투에 repeated로 매핑) List<T>는 전체 길이 - 접두사를 가지고 있지 않기 때문에 ... 여기에 몇 가지 라인 사이에 읽을거야

byte[] 사소한 길이 접두사가 있습니다 추가 버퍼링을 일으키지 않아야합니다. 그래서 내가 당신 실제로이 가지고 더 어떤지 추측하고있어 : 에 기본적으로, A.Foo을 작성할 때 다음

[ProtoContract] 
public class A { 
    [ProtoMember(1)] 
    public B Foo {get;set;} 
} 
[ProtoContract] 
public class B { 
    [ProtoMember(1)] 
    public List<byte[]> Bar {get;set;} 
} 

, 길이 - 접두사에 대한 버퍼를 할 필요가 실제로 선언 "다음과 같은 복잡한 데이터입니다 A.Foo 값 ").당신의 길이를 나타내는 마커를 얻을 의미

  • 기본 (구글의 진술 선호) 길이 접두사입니다 :

    [ProtoMember(1, DataFormat=DataFormat.Group)] 
    public B Foo {get;set;} 
    

    이 protobuf 2 개 포장 기술 사이에 변경 : 다행히 간단한 수정이 메시지는 하위 메시지 페이로드,

  • 을 따르도록하지만 시작 마커, 하위 메시지 페이로드 및 최종 마커를 사용하는 옵션도 있습니다

두 번째 기술인 을 사용할 때을 버퍼링 할 필요가 없으므로 이렇게하지 마십시오. 이것은 동일한 데이터에 대해 약간 다른 바이트를 쓰는 것을 의미하지만, protobuf-net은 매우 관대하며 의 데이터를 행복하게 역 직렬화합니다. 형식입니다. 의미 :이 변경 작업을 수행하면 기존 데이터를 읽을 수 있지만 새 데이터는 시작/종료 마커 기술을 사용합니다.

이렇게하면 질문 : 왜 Google은 길이 접두어 방식을 선호합니까? 아마도입니다. 길이 접두사 접근법을 사용할 때을 읽으면 (원시 리더 API를 통해 또는 원치 않는/예기치 않은 데이터로) 필드를 건너 뛸 때 더 효율적이므로 이므로 길이 접두사를 읽을 수 있습니다 , 그런 다음 스트림 [n] 바이트 만 진행하면됩니다. 반대로 Start/End 마커가있는 데이터는 건너 뛰고 페이로드를 크롤링해야하며 하위 필드는 개별적으로 건너 뜁니다. 물론,이 이론상의 읽기 성능 차이는 이 해당 데이터 인을 기대하고 실제로 읽는 경우에 적용되지 않습니다. 또한 Google protobuf 구현에서는 일반 POCO 모델과 작동하지 않기 때문에 페이로드 크기가 이미 알려져 있으므로 서면 작성시 동일한 문제가 실제로 발생하지 않습니다.

+0

빠른 답장을 보내 주셔서 감사합니다. 우리의 데이터 구조에 대한 귀하의 추측은 옳았습니다. A에 대한 참조를 포함하는 모든 속성에 대해 DataFormat을 Group으로 변경해야하며 객체 그래프의 루트까지도 변경해야한다고 말하는 것이 옳은가요? 그리고이 변경은 또한 관련 ProtoInclude 속성에도 있어야합니다. –

+0

@James는 본질적으로 그렇습니다. 음 ... 아마도 모델 레벨의 기본값을 추가해야 할 것입니다! –

+0

DataFormat.Group을 사용하여 문제를 해결하기 위해 시도한 내용으로 내 질문을 업데이트했지만 버퍼를 비우는 데 여전히 문제가 있습니다. 내가 바보가된다면 사과한다. .. –

2

추가 편집; [ProtoInclude(..., DataFormat=...)]은 단순히 처리되지 않는 것처럼 보입니다. 나는 나의 현재 로컬 빌드에서 이것에 대한 테스트를 추가하고, 지금 전달합니다

[Test] 
public void Execute() 
{ 

    var a = new A(); 
    var b = new B(); 
    a.B = b; 

    b.Data = new List<byte[]> 
    { 
     Enumerable.Range(0, 1999).Select(v => (byte)v).ToArray(), 
     Enumerable.Range(2000, 3999).Select(v => (byte)v).ToArray(), 
    }; 

    var stream = new MemoryStream(); 
    var model = TypeModel.Create(); 
    model.AutoCompile = false; 
#if DEBUG // this is only available in debug builds; if set, an exception is 
    // thrown if the stream tries to buffer 
    model.ForwardsOnly = true; 
#endif 
    CheckClone(model, a); 
    model.CompileInPlace(); 
    CheckClone(model, a); 
    CheckClone(model.Compile(), a); 
} 
void CheckClone(TypeModel model, A original) 
{ 
    int sum = original.B.Data.Sum(x => x.Sum(b => (int)b)); 
    var clone = (A)model.DeepClone(original); 
    Assert.IsInstanceOfType(typeof(A), clone); 
    Assert.IsInstanceOfType(typeof(B), clone.B); 
    Assert.AreEqual(sum, clone.B.Data.Sum(x => x.Sum(b => (int)b))); 
} 

이 커밋은 다른 관련없는 리팩토링 (WinRT/iKVM을 일부 재 작업)에 연결되어 있지만, 최대한 빨리 노력하여야한다.