Есть ли хорошее время для использования int32 вместо sint32 в буферах протокола Google? - PullRequest
15 голосов
/ 19 апреля 2009

Недавно я читал о буферах протокола Google , что позволяет использовать в сообщениях различные типы скалярных значений.

Согласно их документации , существует три типа целочисленных примитивов переменной длины - int32, uint32 и sint32. В своей документации они отмечают, что int32 «Неэффективно для кодирования отрицательных чисел - если ваше поле может иметь отрицательные значения, используйте вместо него sint32». Но если у вас есть поле, в котором нет отрицательных чисел, я предполагаю, что uint32 будет лучше использовать в любом случае, чем int32 (из-за дополнительного бита и снижения затрат ЦП на обработку отрицательных чисел).

Так, когда int32 будет хорошим скаляром для использования? Подразумевает ли документация, что она наиболее эффективна, только когда вы редко получаете отрицательные числа? Или всегда предпочтительнее использовать sint32 и uint32, в зависимости от содержимого поля?

(Те же вопросы относятся и к 64-разрядным версиям этих скаляров: int64, uint64 и sint64; но я упустил их из описания проблемы для удобства чтения.)

Ответы [ 2 ]

21 голосов
/ 20 апреля 2009

Я не знаком с буфером протокола Google, но моя интерпретация документации:

  • используйте uint32, если значение не может быть отрицательным
  • используйте sint32, если значение скорее всего будет отрицательным, чем нет (для некоторого нечеткого определения "как можно скорее")
  • используйте int32, если значение может быть отрицательным, но это намного менее вероятно, чем значение, являющееся положительным (например, если приложение иногда использует -1, чтобы указать ошибку или «неизвестное» значение, и это относительно редко ситуация)

Вот что должны сказать документы о кодировках (http://code.google.com/apis/protocolbuffers/docs/encoding.html#types):

существует важное различие между типами int со знаком (sint32 и sint64) и типами "standard" int (int32 и int64), когда речь идет о кодировании отрицательных чисел. Если вы используете int32 или int64 в качестве типа для отрицательного числа, результирующий varint всегда будет длиной в десять байтов - фактически он обрабатывается как очень большое целое число без знака. Если вы используете один из типов со знаком, результирующее varint использует кодировку ZigZag, что гораздо более эффективно.

Кодирование ZigZag отображает целые числа со знаком в целые числа без знака, так что числа с небольшим абсолютным значением (например, -1) также имеют небольшое кодированное значение varint. Он делает это таким образом, что «зигзаги» взад и вперед через положительные и отрицательные целые числа, так что -1 кодируется как 1, 1 кодируется как 2, -2 кодируется как 3, и так далее ...

Таким образом, похоже, что даже если вы используете отрицательные числа редко, поскольку величина чисел (включая неотрицательные числа), которые вы передаете в протоколе, находится на меньшей стороне, вам может быть лучше используя sint32. Если вы не уверены, профилирование будет в порядке.

2 голосов
/ 22 февраля 2018

Очень мало веских причин использовать int * вместо sint *. Существование этих дополнительных типов, скорее всего, связано с историческими причинами обратной совместимости, которые протоколные буферы пытаются поддерживать даже в своих собственных версиях протокола.

Мое лучшее предположение состоит в том, что в самой ранней версии они тупо кодировали отрицательные целые числа в представлении дополнения 2, что требует кодирования varint максимально размера 9 байтов (не считая байта дополнительного типа). Затем они застряли с этой кодировкой, чтобы не сломать старый код и сериализации, которые уже использовали его. Таким образом, им нужно было добавить новый тип кодирования, 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): девять байтов

Если вы хотите компактно подобным образом кодировать отрицательные целые числа малой величины, вам потребуется «использовать» один бит для обозначения знака. Вы можете сделать это через явный бит знака (в некоторой зарезервированной позиции) и представление величины. Или вы можете выполнить зигзагообразное кодирование, которое фактически делает то же самое, сдвигая левую величину на 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 * очень редко будет превосходить и, как правило, даже не будет стоить дополнительной мысли, которую вы должны посвятить оценке того, стоит ли это того или нет, ИМХО.

...