この前から、gRPC Go for Professionals を読んでいます。
前回の記事ではEncoding details、どのようにエンコーディングされるのか?については、すっ飛ばしました。
今のところ、すぐすぐ理解する必要はないなと判断したからです。また公式Docsにもアプリケーション作るだけであれば、Encoding detailsについては理解しなくても的な内容がありました。
が、気になる。
ここをすっ飛ばしてしまっては、面白いところを味わうことができない気がしてきました。
というわけでEncodingについて書いていきます。
今回は、こちらの公式Docsの↑のページを読んでいきます。
Encoding
公式Docsでは、Test1というスキーマを例に解説が始まります。
message Test1 { optional int32 a = 1; }
このmessageがどのようにエンコードされるかですが、
When a message is encoded, each key-value pair is turned into a record consisting of the field number, a wire type and a payload.
とあります。
まずは、
- field number
- wire type
- payload
の3つの用語から整理していきましょう。
field number, a wire type and a payload
message Person { optional string name = 1; optional int32 id = 2; optional string email = 3; }
この例でいうと、optional string name = 1;
の1
がfield numberです。Message Structureの中で一意で割り振ります。
次に、a wire type 。
a wire type は6種類あり、VARINT, I64, LEN, SGROUP, EGROUP, and I32がそれ。
たとえば、VARINTには、int32, int64, uint32, uint64, sint32, sint64, bool, enumがある。
optional string name = 1;
の例で言うと、LENが(string, bytes, embedded messages, packed repeated fields)該当する。
最後に、payloadは、本体部分です。
公式Docsでは、a
に150
をセットするという想定で解説していますが、150
がpayloadにあたります。
Encoding payload
では、まずはpayloadのEncodingから詳細を見ていきましょう。
message Test1 { optional int32 a = 1; }
このmessageのa
に150
という値をセットしてシリアライズすると、
08 96 01
という3bytesにエンコードされます。
150が96 01にエンコーディングされます。08の部分については後ほど見ていきます。
150が96 01にエンコードされるアルゴリズム
最初に、150が96 01にエンコードされるアルゴリズムについて見ていきましょう。
まずは、150をバイナリに変換してみましょう。
150は
10010110
です。
varintのエンコーディングでは、
というルールで動きます。
つまり、10010110
で
↓msb 1 0010110 ↑ payload
という分け方です。
で、下位7bitの0010110では、150のバイナリとしては不十分なので、もう1byte使って表現する必要があります。
結果は、10010110 00000001
となります。
ん?
00000001 10010110
じゃないの?って思いましたよね。
これは、variantsが、little-endian orderでエンコーディングするからですね。
10010110 00000001を150に
では、次になぜ10010110 00000001
が150になるのか?について、見ていきます。
考え方的にはシンプルです。
公式のDocsからそのまま掲載します。
10010110 00000001 // Original inputs. 0010110 0000001 // Drop continuation bits. 0000001 0010110 // Convert to big-endian. 00000010010110 // Concatenate. 128 + 16 + 4 + 2 = 150 // Interpret as an unsigned 64-bit integer.
まずは、continuation bits.、つまり先頭の1bitを削除します。
次に、エンコード時にlittle-endian orderで表現されていたデータをbig-endianに変換します。
くっつけると、00000010010110
になるというわけですね。
ここまで来れば、150が96 01
に変換されるのが腑に落ちますね。
150を、エンコーディングすると
10010110 00000001
でした。
この2byteをそれぞれ16進数に変換すると、
10010110 →96 00000001 →01
になるというわけです。
では、続いて08 96 01
の08
について見ていきます。
Encoding tag
再度、こちらの文章を確認しますと、
When a message is encoded, each key-value pair is turned into a record consisting of the field number, a wire type and a payload.
08 96 01
のうち、96 01
が150(payload)なので、08
はthe field number, a wire typeを表現していることになります。
08の部分をtagと公式では読んでいますのでこれ以降はtagと呼びます。
formula of Encoding tag
tagのエンコードの式は、
(field_number << 3) | wire_type
です。
具体的に式に数字を当てはめてみましょう。
(field_number << 3) | wire_type (1 <<< 3) | 0
です。
(field_number << 3)
まずは、(field_number << 3)から計算してみましょう。
field_numberの1は、bitに直すと00000001
です。
<< 3なので、3bit分、左にシフトしまして、結果は00001000
となります。00001000
です。
| wire_type
次に、wire_typeとのOR演算をしていきます。variantsのwire_typeは0です。
0とOR演算しても、元の数値は変わりませんよね。
ということで、結果は00001000
、08
になるというわけです。
decode
では、最後にdecodeについて見てみましょう。
In other words, after decoding the varint representing a field, the low 3 bits tell us the wire type, and the rest of the integer tells us the field number.
decodeする場合には、下位3bitに対して0x07(00000111)をAND演算します。そうすると、wire_typeを導き出すことができます。
で、上位5bitがfield_numberを表現してるのでdecodeは簡単ですね。
まとめ
以上、Encoding details Variants編をみていきました。
2進数とかビット演算など、不慣れで理解に時間がかかりましたが、紐解いてみると各所はやっていることはシンプルですね。
理解すると気持ち良きですが、「じゃあ、gRPCの構築にどう役立てるんだい??」と自問するとよくわからんですね。
ひとまずは、Encoding details Variantsを理解したということで次は、その他のエンコードについてみていきたいと思います。
僕から以上。あったかくして寝ろよ。