30

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

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

那么什么时候int32是一个好的标量呢?文档是否暗示只有当您很少得到负数时它才最有效?还是总是更喜欢使用sint32and uint32,具体取决于字段的内容?

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

4

2 回答 2

42

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

  • uint32如果值不能为负,则使用
  • sint32如果该值很可能是负数,则使用(对于“可能是”的一些模糊定义)
  • 如果值可能为负,则使用int32该值,但这比正值的可能性要小得多(例如,如果应用程序有时使用 -1 表示错误或“未知”值,这是一种相对不常见的情况)

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

在编码负数时,带符号的 int 类型( sint32and sint64)和“标准” int 类型(int32and )之间存在重要区别。int64如果你使用int32orint64作为负数的类型,结果varint总是十个字节长——实际上,它被视为一个非常大的无符号整数。如果您使用其中一种有符号类型,则结果varint将使用 ZigZag 编码,这种编码效率更高。

ZigZag 编码将有符号整数映射到无符号整数,因此绝对值小的数字(例如 -1)也具有小的varint编码值。它以一种通过正整数和负整数来回“曲折”的方式执行此操作,因此 -1 被编码为 1,1 被编码为 2,-2 被编码为 3,依此类推......

因此,即使您很少使用负数,只要您在协议中传递的数字(包括非负数)的幅度较小,您最好还是使用sint32. 如果您不确定,分析将是有序的。

于 2009-04-19T20:51:02.760 回答
6

几乎没有充分的理由使用 int* 而不是 sint*。这些额外类型的存在很可能是出于历史的、向后兼容的原因,Protocol Buffers 甚至在其自己的协议版本中都试图维护它们。

我最好的猜测是,在最早的版本中,他们以 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):九个字节

为了说明在 sint* 上使用 int* 的情况,负数必须非常罕见,但可能,和/或您期望编码的最常见的正值必须正好落在导致的切点之一附近与 int* 相比,在 sint* 中进行更大的编码(例如 - 2^6 对 2^7 导致 2x 编码大小)。

基本上,如果你要得到一些可能是负数的数字,那么默认使用 sint* 而不是 int*。int* 很少会出类拔萃,而且通常甚至不值得您花费额外的精力来判断它是否值得恕我直言。

于 2018-02-22T04:35:49.740 回答