固定大小缓冲区的序列化

Serialization of fixed size buffers

我有一个包含 14,400,360 个结构的数组,每个结构包含 3 个字节的数据。

[ProtoContract]
struct A
{
    [ProtoMember(1)]
    private unsafe fixed byte[3] data;
}

不幸的是,protobuf-net 2.0.0.668 无法序列化固定大小的缓冲区,并在序列化时抛出异常。 (类似于 "no serializer for FixedArray") 我认为答案已经解释 here.

我目前的解决方法是将固定数组拆分为三个单独的字节并修复布局。

[ProtoContract]
[StructLayout(LayoutKind.Explicit, Size = 3, CharSet = CharSet.Ansi)]
struct A
{
    [ProtoMember(1)]
    [FieldOffset(0)]
    private byte data;

    [ProtoMember(2)]
    [FieldOffset(1)]
    private byte data1;

    [ProtoMember(3)]
    [FieldOffset(2)]
    private byte data2;
}

问: 是否有一些黑魔法或者我遗漏的简单技巧,所以我不必手动拆分数组?

在测量包含此数组的对象的堆使用情况时,它占用 43,201,160 字节或每个结构仅占用 3 个字节。 磁盘上的序列化文件占用 72,814,584 字节或每个结构约 5.05 字节。

问: 什么占用了每个结构额外的 2 个字节? 我没试过,但也许可以通过制作一个 3*14,400,360 字节的数组来减少序列化的大小? (万不得已)

编辑:更正 序列化文件的大小为 126,246,995 字节或每个结构 8.8 字节,而不是最初报告的每个结构 5.5 字节。

编辑:跟进 使用 中的单一成员技巧将文件大小降低到 90,952,228 字节或每个结构 6.3 字节。

有趣的问题。我喜欢有趣的问题!

在 protobuf 中没有 完美的 方式来表示它 - 因为 pre-defined 格式没有固定大小数据类型的概念。我们有多种选择:

  • 每个元素使用一个字段,但每个元素的成本为 header+ 值,大小加倍(或更多,对于大字段数)——不是一个很好的选择(供参考,我认为原因你看到 5.05 是因为它跳过了任何零值)
  • 使用 length-prefixed 块(bytesrepeated packed) - 但仍然需要 header 加上长度加上有效负载 - 所以:你的 5 个字节案件;还有一些尴尬的问题是如果我们正在反序列化数据并且它 多于 3 个字节,这不应该发生,但是:我是一个图书馆作者和很多不该发生的事情:发生了
  • 使用单个固定大小的整数格式,因此:header 加上 4 个字节 = 5 个字节
  • 使用单一的 varint 整数格式,因此:header 加上 1-4 个字节(24 位可以占用 1-4 个字节,因为 varint 是 7 位加上连续)

在所有这些中,还将有一个 object 包装所有这些,这将占用 2 个字节。

考虑到这些选项,我认为后者将是你最好的选择——你可以通过一个成员来做到这一点:

[ProtoMember(1)] // varint by default
private uint SerializedValue {
    get { /* pack bits from the field and return */ } }
    set { /* unpack "value" into the field */ }
}

这大概是您今天所能做的最好的事情了;然而,对我来说,考虑改进 vFuture 是一个有趣的场景——也许可以让我们避免外部 object 开销;基本上将 整个事物 表示为单个二进制字符串 - 不需要内部字段标记。


但是,我想知道这里理想的解决方案是向上移动一个级别,以便A本身的数组/列表可以被视为thunkable并且"packed"。这意味着我们基本上有类似的东西:

WriteFieldHeaderWithLengthPrefix(1, WireType.String, arr.Length * 3);
foreach(var item in arr) Append(item); // writes 3 bytes

和:

var arr = new A[ReadLengthPrefix() / 3];
for(int i = 0 ; i < arr.Length; i++)
    arr[i] = Parse(item); // reads 3 bytes

这个在当前代码中不可用,但它可能在新代码中可用的东西 - 我们有自定义序列化程序的概念。这当然是我要探索的领域。