protobuf-net消息序列化大小属性

Bor*_*vic 8 c# protobuf-net

我们使用protobuf-net对公共协议基于Google Protocol Buffers的应用程序中的消息进行序列化和反序列化.该库非常出色,涵盖了除此之外的所有要求:我们需要在消息实际序列化之前找出序列化的消息长度(以字节为单位).

这个问题已经在一年半前提出过,根据Marc的说法,唯一的方法就是序列化到MemoryStream并.Length随后读取属性.在我们的例子中这是不可接受的,因为MemoryStream在幕后分配一个字节缓冲区,我们必须避免这种情况.

来自同一回复的这一行给了我们希望它毕竟是可能的:

如果你澄清用例是什么,我相信我们可以轻松地提供它(如果它还没有).

这是我们的用例.我们有大小在几个字节和两兆字节之间变化的消息.应用程序预先分配用于套接字操作和序列化/反序列化的字节缓冲区,一旦预热阶段结束,就不能创建额外的缓冲区(提示:avoding GC和堆碎片).字节缓冲区基本上是合并的.我们还希望尽可能避免在缓冲区/流之间复制字节.

我们提出了两种可能的策略,它们都需要预先设置邮件大小:

  1. 使用(大)固定大小的字节缓冲区并序列化所有可以放入一个缓冲区的消息; 使用发送缓冲区的内容Socket.Send.我们必须知道下一条消息何时无法放入缓冲区并停止序列化.如果没有消息大小,实现此目的的唯一方法是等待异常发生Serialize.
  2. 使用(小)可变大小的字节缓冲区并将每条消息序列化为一个缓冲区; 使用发送缓冲区的内容Socket.Send.为了从池中检出具有适当大小的字节缓冲区,我们需要知道序列化消息有多少字节.

因为协议已经定义(我们不能改变它)并且要求消息长度前缀为Varint32,所以我们不能使用SerializeWithLengthPrefix方法.

那么是否可以添加一种估计消息大小而无需序列化到流中的方法?如果它不适合图书馆的当前功能集和路线图,但是可行,我们有兴趣自己扩展图书馆.我们也在寻找替代方法,如果有的话.

Mar*_*ell 6

如前所述,这不是立即可用的,因为代码有意尝试对数据进行一次传递(尤其是IEnumerable<T>等)。但是,根据您的数据,它可能已经进行了适度的复制,以考虑到子消息也是长度前缀的事实,因此可能需要处理。这种杂耍可以通过在消息内部使用“分组”子格式来大大减少,因为组允许仅向前构造而没有回溯。

那么是否可以添加一种无需序列化即可估计消息大小的方法到流中?

估计几乎没用;因为没有终止符,所以它需要是准确的。最终,如果不实际去做,尺寸是有点难以预测的。v1 中有一些代码用于大小预测,但目前似乎首选单遍代码,并且在大多数情况下,缓冲区开销是名义上的(有代码可以重新使用内部缓冲区,以便它不会花费所有为小消息分配缓冲区的时间)。

如果您的消息内部仅转发(分组),那么作弊可能是序列化为测量的假流,但会丢弃所有数据;但是,您最终会序列化两次。

关于:

并且要求消息长度前缀为 Varint32,我们不能使用SerializeWithLengthPrefix方法

我不太确定我在那里看到了这种关系 - 它允许在此处使用一系列格式等;也许你能更具体一点?

重新复制数据 - 我在这里玩的一个想法是对长度前缀使用次规范形式。例如,可能在大多数情况下 5 个字节就足够了,所以它可以留下 5 个字节而不是 juggle,然后简单地覆盖而不压缩(因为八位字节10000000仍然意味着“零并继续”,即使它是多余的) . 这仍然需要缓冲(以允许回填),但不需要数据的移动。

最后一个简单的想法很简单:序列化为 a FileStream; 然后写入文件长度和文件数据。很明显,它用内存来换取 IO。