Open8

go/protobufを読む

nabeyangnabeyang

Varintへのエンコーディングとデコーディング

/encoding/protowire/wire.goを見ると AppendVarintでエンコーディングし、ConsumeVarintでデコーディングするっぽい。最適化の関係なのか、ベタ書きになってるけど、次のように書くと意味がとりやすいかも。

func AppendVarint(b []byte, v uint64) []byte {
	for i := 0; i < 10; i++ {
		if v < 1<<(7*(i+1)) || i == 9 {
			n := i + 1
			bb := make([]byte, n)
			for i := 0; i < (n - 1); i++ {
				bb[i] = byte(v>>(7*i)&0x7f | 0x80)
			}
			if i < 9 {
				bb[n-1] = byte(v >> (7 * i))
			} else {
				bb[n-1] = 1
			}
			b = append(b, bb...)
			break
		}
	}
	return b
}

func ConsumeVarint(b []byte) (uint64, int) {
	var v uint64
	n := len(b)
	for i := 0; i < 10; i++ {
		if n == i {
			return 0, errCodeTruncated
		}

		y := uint64(b[i])
		v += y << (7 * i)

		if i != 9 {
			if y < 0x80 {
				return v, (i + 1)
			}
			v -= 0x80 << (7 * i)
		} else if y < 2 {
			return v, (i + 1)
		}
	}

	return 0, errCodeOverflow
}

7bit毎に分割して、後続ありかどうかを0x80のフラグで管理している。64bitだと偶然7bitで分割すると、割と綺麗になる。次の要して7bit分割したのに1足すとUInt64の最大値が作れる。

	var a uint64
	for i := 0; i < 9; i++ {
		a += 0x7f << (7 * i)
	}
	a += 1 << 63
	fmt.Println(a)
	fmt.Println(a == math.MaxUint64)// => true

実はencoding/binaryUvarintPutUvarintを使っても良さそう。

nabeyangnabeyang

タグ

protoc-gen-goから生成されるメッセージは次のような感じだけど

type Str struct {
	state protoimpl.MessageState
	sizeCache     protoimpl.SizeCache
	unknownFields protoimpl.UnknownFields

	Value string `protobuf:"bytes,1,opt,name=value,proto3"`
}

レガシーモードでしか関係なくて、次のようにしても影響ない。

type Str struct {
	state protoimpl.MessageState
	sizeCache     protoimpl.SizeCache
	unknownFields protoimpl.UnknownFields

	Value string `protobuf:"1"`
}

次のような感じにしてメッセージの順序を取ってる。
https://github.com/protocolbuffers/protobuf-go/blob/a8d446d548e4cfda41570959bd99c90365c2b995/internal/impl/message.go#L179-L185