Protobufを勉強していたら、良いアルゴリズムを見つけました - ZigZag

Protobufを勉強していたら、良いアルゴリズムを見つけました - ZigZag

[[434311]]

もともと Protobuf の原理を勉強したかったのですが、研究の過程で Protobuf が負の数をエンコードするときに ZigZag アルゴリズムを使用していることがわかったので、この記事を書きました。もちろん、Protobuf を理解していなくても、読むのにまったく影響はありません。

このアルゴリズムについて説明する前に、基本コードと補数コードについて説明しましょう。理解できたら下を指さしてください。

コード:

https://github.com/miaogaolin/gofirst/tree/main/zigzag

ベース

いわゆるベースとは、特定のビットの情報がいっぱいになったときに、それを前方に移動する必要があることを意味します。たとえば、10 進法では、ある位置の数が 10 に達すると繰り上がりが行われ、2 進法では、ある位置の数が 2 に達すると繰り上がりが行われます。

キャリーは相互に変換できます。例:

10進数: 10 → 2進数: 1010 → 16進数: A

以前、「なぜ 10 進法がより一般的に使用されているのですか?」という回答を見たことがあります。

人間には 10 本の指しかなく、正確に 10 個の数字を数えることができるため、10 進法が自然とデフォルトのシステムになります。人間に 11 本の指があるなら、おそらく 11 進数になるでしょう。

その後、コンピュータの登場により、データの有無が最も自然な情報伝達単位となり、0と1からなる2進数が自然にコンピュータの数値体系となりました。これを基に、情報の利用を容易にするために、8進数と16進数が採用されました。

さて、バイナリシステムについて言うべきことは以上です。

3つのこと

次に、10 進数の正の整数を 2 進数で表します。たとえば、10 進数の 10 は 2 進数の 1010 に等しくなります。

バイナリが負の数を表す場合はどうなるでしょうか?降りてきて話しましょう。

コンピュータの世界では、オリジナルコード、リバースコード、補完コードの概念が定義されています。説明を簡単にするために、1 バイト (1 バイト = 8 ビット) を使用して数値を表すと仮定します。

1. オリジナルコード

最初のビットは符号(非負数の場合は 0、負数の場合は 1)を表し、残りのビットは値を表します。例えば:

+8 → オリジナル: 00001000

-8 → オリジナル: 10001000

2. 逆コード

最初のビットは符号を示すために使用され(非負の数の場合は 0、負の数の場合は 1)、残りのビットは非負の数の場合は変更されず、負の数の場合はビットごとに反転されます。例えば:

+8 → オリジナル: 0000 1000 → リバース: 0000 1000

-8 → オリジナル: 1000 1000 → リバース: 1111 0111

整数のバイナリを表現するために元のコードまたは補数コードを使用する場合、何か問題がありますか?表面的には問題ないように見えます。しかし、よく考えてみると、2 つの問題があることがわかります。

まず、0 は実際には +0 と -0 の 2 つのコードで表すことができます。

オリジナル: 0000 0000 → 1000 0000

逆順: 0000 0000 → 1111 1111

第二に、コンピュータは符号ビットの存在を認識しないため、計算後に奇妙な現象が発生します。

オリジナルコード

1 + (-1)

→ 0000 0001 + 1000 0001

→ 1000 0010

→ -2

これは明らかに間違っています!

逆コード

1 + (-1)

→ 0000 0001 + 1111 111

→ 1111 1111

→ -0

これも変な感じですね。

そこで、これらの問題を解決するために、コンピュータシステムに補完コードを導入しました。

3. 補完

最初の桁は符号を示すために使用し(非負の数の場合は 0、負の数の場合は 1)、非負の数の残りの桁は変更されず、負の数の場合はビットごとに反転され、最後の桁に 1 が追加されます。

+8 → オリジナル: 0000 1000 → 追加: 0000 1000

-8 → オリジナル: 1000 1000 → 追加: 1111 1000

次に、計算に記号を導入すると何が起こるかを見てみましょう。

1 + (-1)

→ 0000 0001 + 1111 1111

→ 0000 0000

→ 0

明らかに、この方法では、コンピュータが計算を実行するときに、符号の問題を心配する必要がなく、すべての計算は、2 がいっぱいになると 1 を追加するという規則に従って処理されます。

さて、これで基数と補数に関する知識はすべて終わりです。それでは本題に入りましょう。

ジグザグ

ほとんどの場合、使用する整数は小さい傾向があります。

システム通信中の送信を容易にするために、基本的な送信タイプとして整数 (int32、int64) が必要になることがよくあります。

したがって、ほとんどのシステムでは、4 バイトと 8 バイトで表されます。このように、整数 1 を送信するには、「00000000 00000000 00000000 00000001」の 32 ビットを送信する必要がありますが、貴重なデータは 1 ビットだけであり、残りは無駄になります。

ではどうすればいいでしょうか? ZigZagアプリケーションが誕生しました。では、このアルゴリズムの具体的なアイデアは何でしょうか?

正の整数の場合、データを送信するときに、冗長な 0 を削除するか、意味のない 0 を可能な限り削減します。それで、データを圧縮できますか?ではどうやって圧縮するのでしょうか?

答えも非常に簡単です。たとえば、数字は 00000000 00000000 00000000 00000001 です。 1 ビットまたは 1 バイト 00000001 のみを送信すると、無駄なデータが大幅に削減されますか?

もちろん、世界に正の整数しか存在しないのであれば、これは簡単に実行できます。残念ながら、彼は実際にはマイナスの数を持っています!負の数をどのように表すのでしょうか?

たとえば、-1 の 10 進補数表現は 11111111 11111111 11111111 1111111 です。

ご覧のとおり、最初の部分はすべて 1 です。どうすればよいと思いますか?

ZigZag は非常に巧妙な方法を提供します。前に述べたように、補数コードの最初のビットは符号ビットであり、先頭の 0 を圧縮することができません。そのため、この符号ビットを補数コードの最後に配置し、他のビットを 1 ビット前方に移動します。

補足: ** 1 **1111111 11111111 11111111 11111111

→ 符号シフト: 11111111 11111111 11111111 111111** 1 **

しかし、それでも圧縮するのは難しいです。したがって、このアルゴリズムは、負の数のすべてのデータ ビットをビットごとに反転し、符号ビットは変更せずに、次のように整数を取得します。

小数点: -1

→ 補足: ** 1 **1111111 11111111 11111111 11111111

→ 符号シフト: 11111111 11111111 11111111 1111111** 1 **

→ ジグザグ: 00000000 00000000 00000000 0000000** 1 **

負でない整数の場合、符号ビットは末尾に移動され、他のビットは 1 つ前方に移動されますが、データは次のように変更されません。

小数点: 1

→ 補数: ** 0 **0000000 00000000 00000000 00000001

→ 符号シフト: 00000000 00000000 00000000 0000001** 0 **

→ ジグザグ: 00000000 00000000 00000000 0000001** 0 **

このように、正の数、0、負の数はすべて同じように表現できます。小さな整数を圧縮することはできますか?

したがって、次のコードを記述できます。

  1. 関数 int32ToZigZag(n int32) int32 {
  2.  
  3. (n << 1 ) ^ (n >> 31 )を返す
  4.  
  5. }

n << 1 は値全体を 1 つ左にシフトし、値の最後の桁は正数、0、負数のいずれであっても 0 になります。説明は次のとおりです。

小数点: 1

→ 補数: ** 0 **0000000 00000000 00000000 00000001

→ 1ビット左にシフト: 00000000 00000000 00000000 00000010

小数点: -1

→ 補足: ** 1 **1111111 11111111 11111111 11111111

→ 1つ左にシフト: 11111111 11111111 11111111 11111110

n >> 31 は符号ビットを最後に配置します。負でない数値の場合はすべて 0 になり、負の数値の場合はすべて 1 になります。説明は次のとおりです。

小数点: 1

→ 補数: ** 0 **0000000 00000000 00000000 00000001

→ 31ビット右シフト: 00000000 00000000 00000000 0000000** 0 **

小数点: -1

→ 補足: ** 1 **1111111 11111111 11111111 11111111

→ 31ビット右シフト: 11111111 11111111 11111111 1111111** 1 **

最後のステップは巧妙なもので、2 つの値に対してビット単位の XOR 演算を実行します。

小数点: 1

→ n << 1 :00000000 00000000 00000000 00000010 ^

n >> 31 : 00000000 00000000 00000000 0000000** 0 **

→ 00000000 00000000 00000000 0000001** 0 **

最終結果を確認できます。データ ビットは変更されず、符号ビットも変更されませんが、符号ビットは最後のビットに移動します。

小数点: -1

→ n << 1 :11111111 11111111 11111111 11111110 ^

n >> 31 :11111111 11111111 11111111 1111111** 1 **

→ 00000000 00000000 00000000 0000000** 1 **

ご覧のとおり、データ ビットはすべて反転されますが、符号ビットは変更されずに最後のビットに移動します。このステップは本当に巧みに書かれています!

ZigZag 復元コードは次のとおりです。

  1. toInt32関数(zz int32) int32 {
  2.  
  3. int32(uint32(zz)>> 1 )^-(zz & 1 )を返す
  4.  
  5. }

同様に、復元されたコードを逆に記述することもできます。ただし、ここで注意すべき点は、右にシフトする場合は符号なしシフトを使用する必要があることです。そうしないと、最初の桁が 1 の場合、シフト時に 1 が追加されます。したがって、符号なしシフト演算 uint32(zz)>>1 が使用されます。

さて、このアルゴリズムを使用すると、先頭に 0 が付いた整数を取得できます。ただし、データは依然として 4 バイトを使用して保存されます。次に、バイト数をできるだけ減らして復元する方法を検討する必要があります。

たとえば、上記のアルゴリズムに従って 1 を変換すると、次のようになります: 00000000 000000000 00000000 00000010。

先頭の 0 をすべて省略して、2 ビット (10) または 8 ビット (00000010) のみを送信すればよい場合が最適です。データ転送はバイト単位で行われるため、単位は 8 ビットのままにしておくのが最適です。したがって、2 つのアプローチがあります。

次の有効なバイトの長さを示すために、追加のバイトを追加できます。例: 00000001 00000010。最初の 8 ビットは、次に送信するバイトが 1 つあることを示し、次の 8 ビットは実際のデータを示します。この方法では望ましい効果が得られますが、不可解なことに余分なバイトの無駄が追加されます。無駄を避ける方法はあるでしょうか?

バイト自己表現方式。 ZigZag は、バイトがそれ自体を表現する方法を導入します。具体的にはどうすればいいのでしょうか?コードを見てみましょう。

  1. 関数compress(zz int32) []バイト{
  2.  
  3. var res []バイト 
  4.  
  5. サイズ := バイナリ.Size(zz)
  6.  
  7. i := 0の場合; i < サイズ; i++ {
  8.  
  9. (zz & ^ 0x7F ) != 0 の場合{
  10.  
  11. res = append(res,バイ​​ト( 0x80 |(zz& 0x7F )))
  12.  
  13. zz = int32(uint32(zz) >> 7 )
  14.  
  15. }それ以外{
  16.  
  17. res = append(res,バイ​​ト(zz& 0x7F ))
  18.  
  19. 壊す 
  20.  
  21. }
  22.  
  23. }
  24.  
  25. 戻り
  26.  
  27. }

まず、コード内の ^0x7F を見てみましょう。これは一体何でしょうか?

^0x7F: 11111111 11111111 11111111 10000000

下から 8 番目から始まる数字で、上位の数字はすべて 1 です。その機能は、最後の 7 桁を削除した後に情報があるかどうかを判断することです。

この関数に ZigZag 値を渡すと、この関数は値を下位から上位まで 7 ビットのグループに分割します。上位ビットに有効な情報がある場合は、7 ビットに 1 ビット (0x80) が追加されます。このプロセスは、先頭の 0 がすべて揃うまで繰り返され、その後アルゴリズムは終了します。

例えば:

小数点: -1000

→ 補数: ** 1 **1111111 11111111 11111100 00011000

→ ジグザグ: 00000000 00000000 00000111 1100111** 1 **

まず、上記の数字を 7 桁のグループに分けます: 0000 0000000 0000000 0001111 1001111。

詳細な手順は次のとおりです。

^0x7F との AND 演算の結果にはまだ上位ビットの情報が含まれているため、下位 7 ビットを取り出して、最後から 8 番目のビットに 1 (0x80) を追加します: 11001111。

この数値を 7 ビット右にシフトすると、0000 0000000 0000000 0000000 0001111 になります。

次に、最後の 7 ビットを取り出し、^0x7F との AND 演算を実行します。上位ビット (すべて 0) には情報がないことがわかります。次に、最後の 8 ビット (00001111) を完全に取り出し、ループから抜け出してアルゴリズムを終了します。

最終的に、2バイトのデータ[11001111, 00001111]が取得されます。

上記の手順により、4 バイトの ZigZag 変換された数値が 2 バイトのデータに変換されました。ネットワーク経由で送信する場合は、これらの 2 バイトを他のプロセスに直接送信します。他のプロセスがデータを受信した後、データを組み立てて復元できます。具体的な復旧作業は以下のとおりです。

  1. 関数 decompress(zzByte [] byte ) int32 {
  2.  
  3. var res int32
  4.  
  5. i , offset := 0 , 0 ; i < len(zzByte); i, offset = i+ 1 , offset+ 7 {
  6.  
  7. b := zzByte[i]
  8.  
  9. (b & 0x80 ) == 0x80の場合{
  10.  
  11. res |= int32(b& 0x7F ) << オフセット
  12.  
  13. }それ以外{
  14.  
  15. res |= int32(b) << オフセット
  16.  
  17. 壊す 
  18.  
  19. }
  20.  
  21. }
  22.  
  23. 戻り
  24.  
  25. }

全体のプロセスは圧縮の逆です。各バイトについて、まず最上位ビットが 1 (0x80) かどうかを確認します。ある場合は、それが最後のデータ バイト パケットではないことを意味するため、このバイトの最後の 7 ビットをアセンブリに使用します。それ以外の場合は、最後のバイトに到達したことを意味します。その後、アセンブリ後にループが終了し、アルゴリズムが終了します。最終結果は 4 バイトの整数になります。

結論

アルゴリズムはそれほど複雑ではありません。何かが理解できない場合は、ただ観察して注意深く考えてください。これは、Protobuf の原則を理解する上でも重要な部分です。わからないことがあれば、下にメッセージを残してください。

完全なコード:

https://github.com/miaogaolin/gofirst/tree/main/zigzag

<<:  今年のダブルイレブンでは、ドローン、無人運転車、ロボットがすべて配備されます!

>>:  ヘルスケア市場におけるサービスロボットは2027年までに32億ドルに達すると予想

推薦する

AIアライメントを徹底レビュー!北京大学などが800以上の文書から4万語を要約し、多くの著名な学者が執筆を担当した。

要点を一目でAI アライメントは、RLHF/RLAIF などの成熟した基本手法だけでなく、スケーラブ...

中国チームが超伝導において新たな大きな進歩を遂げました! LK-99のような物質は、再現性と検証性を備えた超伝導性を示す。

室温超伝導に新たな進歩はありますか?華南理工大学、中南大学、中国電子科技大学の研究者らは12月19日...

NTRU 1.2 リリース Java 用 NTRU 暗号化アルゴリズム ライブラリ

NTRU 1.2 バージョンには多くの機能強化とバグ修正が含まれていますが、このバージョンは以前のバ...

ArcSoft Open Platformの新しいアルゴリズムは、顔認識セグメンテーションのシナリオの拡張に役立ちます

ArcSoft ビジュアルオープンプラットフォームであるArcFace 3.0の発売以来、アルゴリ...

...

AIトレーニングの裏話を公開:専門家だけでなく、世界中の無数のオフィスワーカーもAIの進化に貢献している

要点: AI システムが学習する前に、入力されたデータにラベルを付ける作業が必要です。これは、自動運...

AIアラインメントを説明する4万語:北京大学と複数の大学チームがアラインメントの包括的なレビューを発表

論文(継続的に更新):arxiv.org/abs/2310.19852 AI アライメント概要ウェブ...

AI時代のセキュリティ情勢にはどのような新たな変化が起こっているのでしょうか?

近年、世界の人工知能産業は急速な発展の勢いを見せており、セキュリティ状況はますます複雑になっています...

人工知能が他に何ができるか知りたいですか?明確な「ベイジアン意識」を持たなければならない

私たちとの会話の中で、多くの読者が、人工知能が予想外の多くのことを実行できることに驚いたと述べていま...

ケータリングロボットが市場発展の時代を先導

[[387119]]近年、ロボット産業の急速な発展に伴い、伝統的な飲食業界も徐々に第二の春を迎えてい...

...

新しいAIプログラミング言語はディープラーニングを超える

MIT の研究者チームは、人工知能の分野を初心者にとってよりアクセスしやすいものにするとともに、専門...

クールなデュオ: AI が金融テクノロジーの進化にどのように役立つかを示す 6 つのケース スタディ

中国では、口座間の送金、銀行ローンの申請、取引の実行にインターネットを利用することが住民にとって日常...

GoogleはGoogleアシスタントを生成AIでアップデートする予定

8月1日、海外メディアは、Axiosの報道によると、GoogleはGoogleアシスタントを生成AI...