2012-07-03 28 views
8

Trong ứng dụng của chúng tôi, chúng tôi có một số cấu trúc dữ liệu trong số những thứ khác chứa một danh sách các byte (hiện đang được xem là List<byte[]>). Chúng tôi chunk byte lên bởi vì nếu chúng ta cho phép các mảng byte được đặt trên heap đối tượng lớn sau đó theo thời gian chúng ta bị phân mảnh bộ nhớ.Sử dụng bộ nhớ tuần tự các mảng byte được ghép nối với Protobuf-net

Chúng tôi cũng bắt đầu sử dụng Protobuf-net để tuần tự hóa các cấu trúc này, bằng cách sử dụng DLL tuần tự được tạo riêng của chúng tôi.

Tuy nhiên chúng tôi đã nhận thấy rằng Protobuf-net đang tạo bộ đệm trong bộ nhớ rất lớn trong khi tuần tự hóa. Liếc qua mã nguồn có vẻ như nó không thể tuôn ra bộ đệm bên trong của nó cho đến khi toàn bộ cấu trúc List<byte[]> đã được viết bởi vì nó cần viết tổng chiều dài ở phía trước của bộ đệm sau đó. Điều này không may làm hỏng công việc của chúng tôi với chunking các byte ở nơi đầu tiên, và cuối cùng cho chúng tôi OutOfMemoryExceptions do phân mảnh bộ nhớ (ngoại lệ xảy ra tại thời điểm Protobuf-net đang cố gắng để mở rộng bộ đệm đến hơn 84k, mà rõ ràng là đặt nó trên LOH, và quá trình sử dụng bộ nhớ tổng thể của chúng tôi là khá thấp).

Nếu phân tích của tôi về cách Protobuf-net hoạt động là chính xác, có cách nào xung quanh vấn đề này không?


Cập nhật

Dựa trên câu trả lời của Marc, đây là những gì tôi đã cố gắng:

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

Sau đó, để serialize nó:

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

Tuy nhiên nếu tôi dính điểm ngắt tại ProtoWriter.WriteBytes() nơi nó gọi DemandSpace() về phía dưới cùng của phương pháp và bước vào DemandSpace(), tôi có thể thấy rằng bộ đệm không bị xóa bởi vì writer.flushLock bằng 1.

Nếu tôi tạo ra một lớp cơ sở cho làm mất thể diện như thế này:

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

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

Sau đó writer.flushLock bằng 2 trong DemandSpace().

Tôi đoán có một bước rõ ràng mà tôi đã bỏ lỡ ở đây để làm với các loại có nguồn gốc?

Trả lời

5

Tôi sẽ đọc giữa một số dòng ở đây ... vì List<T> (ánh xạ như repeated trong protobuf cách nói) không có một tổng thể chiều dài-prefix và byte[] (ánh xạ như bytes) có một tầm thường chiều dài-prefix mà không nên gây thêm bộ đệm. Vì vậy, tôi đoán những gì bạn thực sự có được nhiều hơn như:

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

Ở đây, cần phải đệm cho một chiều dài-prefix là thực sự khi viết A.Foo, về cơ bản để tuyên bố "các dữ liệu phức tạp sau đây là giá trị cho A.Foo ").May mắn thay có một sửa chữa đơn giản:

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

này thay đổi giữa 2 kỹ thuật đóng gói trong protobuf:

  • mặc định (google ưu tiên nêu) là chiều dài-tiền tố, có nghĩa là bạn sẽ có được một dấu hiệu cho thấy chiều dài của thông điệp để làm theo, sau đó tải trọng phụ nhắn
  • nhưng đó cũng là một tùy chọn để sử dụng một khởi điểm đánh dấu, payload tiểu thư, và chấm dứt-marker

Khi sử dụng kỹ thuật thứ hai nó không cần phải đệm, vì vậy: nó không. Điều này có nghĩa là nó sẽ viết các byte hơi khác nhau cho cùng một dữ liệu, nhưng protobuf-net là rất tha thứ, và sẽ vui vẻ deserialize dữ liệu từ hoặc định dạng ở đây. Có nghĩa là: nếu bạn thực hiện thay đổi này, bạn vẫn có thể đọc dữ liệu hiện tại của mình, nhưng dữ liệu mới sẽ sử dụng kỹ thuật điểm đánh dấu đầu/cuối.

Điều này đòi hỏi câu hỏi: tại sao Google thích tiếp cận độ dài tiền tố? Có lẽ điều này là vì nó hiệu quả hơn khi đọc để bỏ qua các trường (qua API đọc thô hoặc dữ liệu không mong muốn/không mong muốn) khi sử dụng tiếp cận độ dài tiền tố, vì bạn có thể đọc tiền tố độ dài , và sau đó chỉ cần tiến hành luồng [n] byte; ngược lại, để bỏ qua dữ liệu bằng dấu đầu/cuối bạn vẫn cần phải thu thập dữ liệu thông qua tải trọng, bỏ qua từng trường con. Tất nhiên, sự khác biệt về mặt lý thuyết trong hiệu suất đọc không áp dụng nếu bạn mong đợi dữ liệu đó và muốn đọc nó vào đối tượng của bạn, điều mà bạn gần như chắc chắn làm. Ngoài ra, trong việc triển khai protobuf của google, bởi vì nó không hoạt động với một mô hình POCO thông thường, kích thước của các tải trọng đã được biết, vì vậy chúng không thực sự thấy cùng một vấn đề khi viết.

+0

Cảm ơn bạn đã trả lời nhanh. Dự đoán của bạn về cấu trúc dữ liệu của chúng tôi là chính xác. Liệu tôi có đúng khi nói rằng chúng ta cần phải thay đổi DataFormat thành Group đối với bất kỳ thuộc tính nào có chứa tham chiếu đến A hay không, và vân vân lên đến gốc của đồ thị đối tượng? Và sự thay đổi này cũng cần phải có trên các thuộc tính ProtoInclude có liên quan? –

+0

@James về cơ bản, vâng. Hmmm ... Có lẽ tôi nên thêm một mặc định mô hình cấp cho điều đó! –

+0

Tôi đã cập nhật câu hỏi của mình với nỗ lực của mình khi sử dụng DataFormat.Group để giải quyết vấn đề nhưng tôi vẫn gặp vấn đề trong việc xóa bộ đệm. Xin lỗi nếu tôi là một thằng ngốc .. –

2

Bổ sung lại chỉnh sửa của bạn; các [ProtoInclude(..., DataFormat=...)] có vẻ như nó chỉ đơn giản là không được xử lý. Tôi đã thêm một thử nghiệm cho điều này trong xây dựng địa phương hiện tại của tôi, và bây giờ qua:

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

này cam được gắn vào một số khác, tái cấu trúc không liên quan (một số làm lại cho WinRT/IKVM), nhưng nên được cam kết càng sớm càng tốt.

Các vấn đề liên quan