我最近一直在Google Protocol Buffers阅读,它允许在消息中使用各种标量值类型。
根据their documentation,有三种类型的可变长度整数基元 - int32
,uint32
和sint32
。在他们的文档中,他们指出int32
“对于编码负数而言效率低 - 如果您的字段可能有负值,请改用sint32
。”但是如果你有一个没有负数的字段,我认为uint32将是一个比int32
更好的类型(由于额外的位和降低处理负数的CPU成本)。
那么什么时候int32
会成为一个好的标量?文档是否意味着只有当您很少得到负数时它才是最有效的?或者,最好使用sint32
和uint32
,具体取决于字段的内容?
(同样的问题也适用于这些标量的64位版本:int64
,uint64
和sint64
;但为了便于阅读,我将它们排除在问题描述之外缘故。)
答案 0 :(得分:21)
我不熟悉Google Protocol Buffers,但我对文档的解释是:
uint32
sint32
如果该值几乎可能是否定为负值(对于某些模糊定义“很可能”)int32
,但这比正值更不可能(例如,如果应用程序有时使用-1来表示错误或'未知'值,那么这是一个相对不常见的情况)以下是有关编码的文档(http://code.google.com/apis/protocolbuffers/docs/encoding.html#types):
签名的int类型(
sint32
和sint64
)和“标准”int类型(int32
和int64
)之间存在重要差异编码负数。如果使用int32
或int64
作为负数的类型,则生成的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 *很少会优越,通常会赢得额外的想法,你必须致力于判断它是否值得恕我直言。