在Google Protocol Buffers中使用int32而不是sint32是不是很好?

时间:2009-04-19 19:12:59

标签: protocol-buffers primitive

我最近一直在Google Protocol Buffers阅读,它允许在消息中使用各种标量值类型。

根据their documentation,有三种类型的可变长度整数基元 - int32uint32sint32。在他们的文档中,他们指出int32“对于编码负数而言效率低 - 如果您的字段可能有负值,请改用sint32。”但是如果你有一个没有负数的字段,我认为uint32将是一个比int32更好的类型(由于额外的位和降低处理负数的CPU成本)。

那么什么时候int32会成为一个好的标量?文档是否意味着只有当您很少得到负数时它才是最有效的?或者,最好使用sint32uint32,具体取决于字段的内容?

(同样的问题也适用于这些标量的64位版本:int64uint64sint64;但为了便于阅读,我将它们排除在问题描述之外缘故。)

2 个答案:

答案 0 :(得分:21)

我不熟悉Google Protocol Buffers,但我对文档的解释是:

  • 如果值不能为负,请使用uint32
  • 使用sint32如果该值几乎可能是否定为负值(对于某些模糊定义“很可能”)
  • 如果值可能为负,则使用int32,但这比正值更不可能(例如,如果应用程序有时使用-1来表示错误或'未知'值,那么这是一个相对不常见的情况)

以下是有关编码的文档(http://code.google.com/apis/protocolbuffers/docs/encoding.html#types):

  

签名的int类型(sint32sint64)和“标准”int类型(int32int64)之间存在重要差异编码负数。如果使用int32int64作为负数的类型,则生成的varint总是十个字节长 - 实际上,它被视为一个非常大的无符号整数。如果您使用其中一种签名类型,则生成的varint使用ZigZag编码,效率更高。

     

ZigZag编码将有符号整数映射到无符号整数,因此具有较小绝对值(例如-1)的数字也具有较小的varint编码值。它通过正负整数来回“zig-zags”的方式做到这一点,因此-1被编码为1,1被编码为2,-2被编码为3,依此类推......

所以即使您使用负数很少见,只要您在协议中传递的数字(包括非负数)的幅度较小,您可能会感觉更好使用sint32。如果您不确定,那么分析将是有序的。

答案 1 :(得分:2)

使用int *而不是sint *的理由很少。这些额外类型的存在很可能是出于历史,向后兼容的原因,协议缓冲区试图在其自己的协议版本中进行维护。

我最好的猜测是,在最早的版本中,他们在2的补码表示中愚蠢地编码负整数,这需要最大大小的9个字节的varint编码(不计算额外的类型字节)。然后他们坚持使用该编码,以免破坏已经使用它的旧代码和序列化。因此,他们需要添加一种新的编码类型sint *,以便在不破坏现有代码的情况下为负数获得更好的可变大小编码。设计师如何从一开始就没有意识到这个问题,这完全超出了我的范围。

varint编码(没有类型规范,需要多1个字节)可以用以下字节数编码无符号整数值:

[0,2 ^ 7]:一个字节

[2 ^ 7,2 ^ 14]:两个字节

[2 ^ 14,2 ^ 21]:三个字节

[2 ^ 21,2 ^ 28]:四个字节

[2 ^ 28,2 ^ 35]:五个字节

[2 ^ 35,2 ^ 42]:六个字节

[2 ^ 42,2 ^ 49):七个字节

[2 ^ 49,2 ^ 56]:八个字节

[2 ^ 56,2 ^ 64]:九个字节

如果你想要紧凑地编码小幅度负整数,那么你将需要"用完"一位表示标志。您可以通过显式符号位(在某个保留位置)和幅度表示来完成此操作。或者,你可以做zig zag编码,它有效地做同样的事情,左移大小1位,减去1为负数(所以最低有效位表示符号:均衡为非负,赔率为负)。

无论哪种方式,整数需要更多空间的切换点现在比较早2:

[0,2 ^ 6]:一个字节

[2 ^ 6,2 ^ 13):两个字节

[2 ^ 13,2 ^ 20]:三个字节

[2 ^ 20,2 ^ 27]:四个字节

[2 ^ 27,2 ^ 34]:五个字节

[2 ^ 34,2 ^ 41]:六个字节

[2 ^ 41,2 ^ 48]:七个字节

[2 ^ 48,2 ^ 55]:八个字节

[2 ^ 55,2 ^ 63):九个字节

为了使案例使用int * over sint *,负数必须非常罕见,但可能,和/或您希望编码的最常见正值必须落在其中一个切换周围导致sint *中的编码更大的点而不是int *(例如 - 2 ^ 6与2 ^ 7导致2x编码大小)。

基本上,如果你要有一些可能是负数的数字,那么默认情况下使用sint *而不是int *。 int *很少会优越,通常会赢得额外的想法,你必须致力于判断它是否值得恕我直言。