デルタ圧縮は、ネットワークを介して送信されるデータ量をどのように削減しますか?


26

多くのゲームでは、送信されるデータの負荷を軽減するために、デルタ圧縮の手法を使用しています。この手法が実際にデータ負荷を下げる方法を理解できませんか?

たとえば、ポジションを送りたいとしましょう。デルタ圧縮を使用しないvector3場合、たとえばエンティティの正確な位置を送信します(30, 2, 19)。デルタ圧縮ではvector3、より小さな数値でを送信します(0.2, 0.1, 0.04)

両方のメッセージがvector3-各フロートに32ビット-32 * 3 = 96ビットである場合、データ負荷を下げる方法がわかりません。

各フロートをバイトに変換してから、バイトからフロートに戻すことができますが、表示される精度エラーが発生します。


あらゆる形式のデルタ圧縮を使用するゲームでネットワークを作成するときは常に、完全に決定論的である必要があります(失敗し、非同期が発生します)。「バイトからフロートへの変換」(またはその他)が精度エラーを引き起こすかどうかは重要ではありません-必要な条件は、同期されたすべてのマシン/ゲームですべてが完全に同じであることです。「精度エラー」がある場合(フルフロートを使用しても避けられない-CPUが私のCPUと同じフロートを使用しない場合)、すべてのマシンで同じである必要があるため、表示されません。目に見える効果を避けるためにタイプを選択しました。
ルアーン

2
@Luaanまたは部分的な絶対状態を時々追加することができます。たとえば、いくつかのエンティティを選択して絶対位置を渡すことができます。プレーヤーに近いエンティティを選択することをお勧めします。
ラチェットフリーク

どういうわけか、私はいくつかの相対的な程度になるこの質問を期待していたrsyncの ...
SAMB

ハフマンコーディング。
ベンフォイト

ミドルアウト男を使用してください
ジョンDemetriou

回答:


41

保存したマルチプレイヤーゲームの読み込み時や、再同期が必要なときなど、ゲームの完全な状態を送信することを避けられない場合があります。しかし、完全な状態を送信することは、通常は回避され、どこでそれがだ、デルタエンコーディングがでてくる一般的に、これはある。すべてのデルタ圧縮が約であること。あなたの例はその状況を実際には説明していません。デルタ圧縮が言及されている理由は、ナイーブな実装はデルタではなく状態を送信することが多いためです。なぜなら、状態は通常、ナイーブなゲーム実装が保存するものだからです。デルタは最適化です。

デルタを使用すると移動しなかったユニットの位置を送信することはありません。それがその精神です。

私たちが何年もペンパルだったと想像してみてください、そして、私は私の記憶を失いました(そしてそれらを読んだ後にあなたのすべての手紙を捨てました)。一連の手紙を通常どおりに続けるのではなく、あなたが私に追いつくために、あなたの人生の全歴史を1つの巨大な手紙で繰り返し書く必要があります。

与えられたケースでは、完全な状態を送信するために必要な大きなビット範囲とは対照的に、デルタをエンコードするために、より少ないビット数を使用することが可能です(コードベースによって異なります)。世界の距離が何キロメートルもあるとすると、位置を正確にエンコードするために32ビットの浮動小数点数が必要になる場合があります。ただし、エンティティに適用可能な最大速度は、1ティックあたり数メートルであるため、わずか8ビットで実行可能です(最大200cmを格納するには2 ^ 8 = 256で十分です)。もちろん、これは浮動小数点の使用ではなく固定であると仮定します...または、固定小数点の煩わしさを望まない場合は、OpenGLのような何らかのハーフ/クォーターフロートを使用します。


7
あなたの答えは私には明らかではありません。動いていないオブジェクトの情報を送信しないのは、デルタエンコーディングの副作用であり、「その精神」ではないように感じます。デルタエンコーディングを使用する本当の理由は、ラチェットフリークの回答でより強調されているようです。
Etsitpab Nioliv

14
@EtsitpabNioliv「The Spirit」は、単に「必要のないものを送らない」です。これはビットレベルまで引き下げることができます-「必要なメッセージをネットワーク経由で取得するために必要な帯域幅だけを使用する」。この答えは、明らかに他のすべての人にとって十分に明らかなようです。ありがとう。
エンジニア

4
@EtsitpabNioliv SVNがサーバー側にファイルを保存する方法を知ったことがありますか?コミットごとにファイル全体を保存するわけではありません。各コミットに含まれる変更、デルタを保存します。「デルタ」という用語は、2つの値のを指すために数学やプログラミングでよく使用されます。私はゲームプログラマーではありませんが、ゲームの使い方がこれほど大きく異なる場合は驚かれることでしょう。アイデアはそれで理にかなっています:すべてではなく、差分のみを送信することにより、送信する必要があるデータ量を「圧縮」します。(その一部が私を混乱させる場合、それは「圧縮」という言葉です。)
jpmc26

1
また、小さい数字はゼロビットの数が多く、適切な圧縮アルゴリズムを使用して送信された情報をエンコード/デコードすると、ネットワーク上で送信されるペイロードがさらに小さくなる可能性があります。
-liggiorgio

22

デルタが間違っています。個々の要素のデルタを見ています。シーン全体のデルタを考える必要があります。

シーンに100個の要素があり、そのうちの1つだけが移動したとします。100個の要素ベクトルを送信すると、99個が無駄になります。本当に送信する必要があるのは1だけです。

ここで、すべての要素ベクトルを保存するJSONオブジェクトがあるとします。このオブジェクトは、サーバーとクライアントの間で同期されます。「そうそう動いた」と決める代わりに。JSONオブジェクトで次のゲームティックを生成し、diff tick100.json tick101.jsonその差分を作成して送信するだけです。クライアント側では、現在のティックのベクトルに差分を適用し、すべて設定されています。

これを行うことで、テキスト(またはバイナリ!)の違いを検出するための何十年もの専門知識を活用し、自分で何かを失うことを心配する必要はありません。理想的には、開発者としてさらに簡単にするために、舞台裏でこれを行うライブラリを使用することも理想的です。


2
diff非効率なハックのような音を使用する。受信側でJSON文字列を保持し、パッチを適用して毎回デシリアライズする必要はありません。2つのキーと値の辞書の差を計算するのは複雑な作業ではありません。基本的には、すべてのキーをループして、値が等しいかどうかを確認するだけです。そうでない場合は、結果のキーと値の辞書にそれらを追加し、最後にJSONにシリアル化された辞書を送信します。シンプルで、長年の専門知識は不要です。diffとは異なり、この方法は次のとおりです。1)古い(置換された)データは含まれません。2)UDPでより適切に再生します。3)改行に依存しない
-gronostaj

8
@gronostajこれは要点を理解するための例でした。私は実際にJSONのdiffを使用することを推奨していません-それが「想定」と言う理由です。
corsiKa

2
「これを行うと、テキスト(またはバイナリ!)の違いを検出する数十年の専門知識を活用し、自分で何かを失うことを心配する必要がなくなります。理想的には、開発者としてより簡単になります。」その部分は間違いなく、だれもそのようなことを合理的にしないだろうときに、diffの使用またはdiffを使用するライブラリの使用を提案しているように聞こえます。私は「差分の」デルタ圧縮を呼び出すことはありません、それだけでデルタ圧縮だ、類似点は表面的
Selali Adobor

最適な差分圧縮と最適な差分圧縮は同じです。コマンドラインのdiffユーティリティはテキストファイル向けであり、おそらく最適な結果が得られない可能性がありますが、デルタ圧縮を行うライブラリを調査することをお勧めします。デルタという言葉については何もおかしくありません。この意味で、デルタと差分は文字通り同じことを意味します。それは長年にわたって失われたようです。
corsiKa

9

非常に多くの場合、別の圧縮メカニズムが、たとえば算術圧縮のようなデルタエンコーディングと組み合わされます。

これらの圧縮スキームは、可能な値が予測可能にグループ化されている場合に非常によく機能します。デルタエンコーディングは、0付近の値をグループ化します。


1
別の例:100個の宇宙船がそれぞれ独自の位置にあり、同じ速度ベクトルで移動している場合、速度を1回送信するだけです(または、少なくとも十分に圧縮する必要があります)。それ以外の場合は、代わりに100のポジションを送信する必要があります。他の人に追加してもらいます。共有状態のロックステップシミュレーションをデルタ圧縮の積極的な形式と見なす場合、速度も送信しません。プレーヤーからのコマンドのみを送信します。繰り返しになりますが、全員が自分で追加できるようにします。
ルアーン

同意する。答えは圧縮に関連しています。
レオポルドサンチク

8

あなたは大体正しいですが、1つの重要なポイントがありません。

ゲーム内のエンティティは多くの属性によって記述され、その位置は1つだけですです。

どのような属性ですか?ネットワークゲームでは、一生懸命に考える必要はありませんが、次のものが含まれます。

  • ポジション。
  • オリエンテーション。
  • 現在のフレーム番号。
  • 色/照明情報。
  • 透明性。
  • 使用するモデル。
  • 使用するテクスチャ。
  • 特殊効果。
  • 等。

これらをそれぞれ個別に選択すると、特定のフレームで変更する必要がある場合、完全に再送信する必要があることを確実に確認できます。

ただし、これらの属性のすべてが同じ速度で変化するわけではありません

モデルは変わりませんか?デルタ圧縮なしでは、とにかく再送信する必要があります。デルタ圧縮を使用する必要はありません。

位置と方向は、より興味深い2つのケースで、通常はそれぞれ3つのフロートで構成されています。任意の2つのフレーム間で、3つのフロートの各セットのうち1つまたは2つだけが変更される可能性があります。直線で移動していますか?回転していませんか?ジャンプしない?これらはすべて、デルタ圧縮を使用せずに完全に再送信する必要がある場合ですが、デルタ圧縮を使用すると、変更したものを再送信するだけで済みます。


8

オペランドと同じサイズのデータ​​構造に結果を保存し、それ以上処理せずに送信するという単純なデルタ計算だけでは、トラフィックを節約できません。

ただし、デルタに基づいて適切に設計されたシステムがトラフィックを節約できる方法は2つあります。

まず、多くの場合、デルタはゼロになります。デルタがゼロの場合、まったく送信しないようにプロトコルを設計できます。明らかに、送信中または送信していないものを示す必要があるため、これにはオーバーヘッドがありますが、全体的には大きな勝利になる可能性があります。

第二に、デルタの値の範囲は通常、元の数値よりもはるかに小さくなります。その範囲はゼロを中心とします。これは、ほとんどのデルタに小さなデータ型を使用するか、完全なデータストリームを汎用の圧縮アルゴリズムに渡すことで利用できます。


6

ほとんどの答えは、全体として状態に変更を送信することに関するデルタエンコーディングについて説明していますが、完全な状態で圧縮する必要があるデータ量を減らすためのフィルタとして使用できる「デルタエンコーディング」と呼ばれるものがあります更新も同様に行われます。これは、質問にあるように、混乱の原因となる場合があります。

数値のベクトルをエンコードする場合、場合によって(整数、列挙など)、個々の要素に可変バイトエンコーディングを使用できます。これらの場合には、各要素に必要なデータ量をさらに削減できます。累積合計として、または最小値と各値とその最小値の差として保存する場合。

たとえば、ベクトルをエンコードする場合、それ{68923, 69012, 69013, 69015}をとしてデルタエンコードできます{68923, 89, 1, 2}。バイトごとに7ビットのデータを格納し、1バイトを使用して別のバイトが来ることを示す単純な可変バイトエンコーディングを使用すると、配列内の個々の要素はそれぞれ送信に3バイトを必要としますが、デルタエンコードバージョン最初の要素に3バイト、残りの要素に1バイトのみが必要です。シリアル化するデータの種類によっては、これはかなり印象的な節約につながる可能性があります。

ただし、これはより多くのシリアル化の最適化であり、ゲームの状態(またはビデオなど)の一部として任意のデータをストリーミングする際に「デルタエンコーディング」について話す場合、一般的に意味するものではありません。他の答えはすでにそれを説明する適切な仕事をしています。


4

また、圧縮アルゴリズムが差分でより良い仕事をすることは注目に値します。他の回答が言及しているように、ほとんどの要素は2つの状態間で同じままであるか、値がわずかに変化します。どちらの場合も、数値のベクトルの差に圧縮アルゴリズムを適用すると、大幅に節約できます。あなたがいてもいない 0の要素を取り除くようなあなたのベクトルに余分なロジックを適用します。

Pythonの例を次に示します。

import numpy as np
import zlib
import json
import array


state1 = np.random.random(int(1e6))

diff12 = np.r_[np.random.random(int(0.1e6)), np.zeros(int(0.9e6))]
np.random.shuffle(diff12) # shuffle so we don't cheat by having all 0s one after another
state2 = state1 + diff12

state3 = state2 + np.random.random(int(1e6)) * 1e-6
diff23 = state3 - state2

def compressed_size(nrs):
    serialized = zlib.compress(array.array("d", nrs).tostring())
    return len(serialized)/(1024**2)


print("Only 10% of elements change for state2")
print("Compressed size of diff12: {:.2f}MB".format(compressed_size(diff12)))
print("Compressed size of state2: {:.2f}MB".format(compressed_size(state2)))

print("All elements change by a small value for state3")
print("Compressed size of diff23: {:.2f}MB".format(compressed_size(diff23)))
print("Compressed size of state3: {:.2f}MB".format(compressed_size(state3)))

それは私に与えます:

Only 10% of elements change for state2
Compressed size of diff12: 0.90MB
Compressed size of state2: 7.20MB
All elements change by a small value for state3
Compressed size of diff23: 5.28MB
Compressed size of state3: 7.20MB

いい例です。ここでは圧縮が役割を果たします。
レオポルドサンチク

0

位置がvector3に格納されているが、実際のエンティティは一度に少数の整数しか移動できない場合。次に、整数で送信するよりも、バイトで差分を送信する方が適切です。

現在位置:23009.0、1.0、2342.0(3フロート)
新しい位置:23010.0、1.0、2341.0(3フロート)
デルタ:1、0、-1(3バイト)

毎回正確な位置を送信する代わりに、デルタを送信します。


0

デルタ圧縮は、デルタでエンコードされた値の圧縮です。デルタエンコーディングは、数値の異なる統計分布を生成する変換です。分布が選択した圧縮アルゴリズムに適している場合、データ量が低下します。これは、2つの更新間でエンティティがわずかしか移動しないゲームのようなシステムで非常にうまく機能します。

2Dに100個のエンティティがあるとします。512 x 512の大きなグリッド上。例のために整数のみを考慮しています。これは、エンティティごとに2つの整数値または200の数値です。

2回の更新の間に、すべての位置が0、1、-1、2、または-2のいずれかで変化します。0の100インスタンス、1および-1の33インスタンス、2および-2の17インスタンスのみがありました。これはかなり一般的です。圧縮にはハフマンコーディングを選択します。

このためのハフマンツリーは次のようになります。

 0  0
-1  100
 1  101
 2  110
-2  1110

すべての0は単一ビットとしてエンコードされます。それはわずか100ビットです。66個の値は3ビットとしてエンコードされ、34個の値は4ビットとしてエンコードされます。それは434ビットまたは55バイトです。さらに、ツリーが小さいため、マッピングツリーを保存するための小さなオーバーヘッドがあります。5つの数値をエンコードするには、3ビットが必要であることに注意してください。ここでは、「-2」に4ビットを使用する必要があるため、「0」に1ビットを使用する機能を交換しました。

次に、これを200個の任意の番号の送信と比較します。エンティティを同じタイル上に配置できない場合、悪い統計分布が得られることがほぼ保証されます。最良のケースは、100個の一意の番号です(すべて同じXに異なるYを使用)。これは、数字あたり少なくとも7ビット(175バイト)であり、圧縮アルゴリズムにとっては非常に困難です。

エンティティが少ししか変更されない特別な場合に、デルタ圧縮が機能します。多くの独自の変更がある場合、デルタエンコーディングは役に立ちません。


デルタエンコーディングと圧縮は、他の変換を伴う他の状況でも使用されることに注意してください。

MPEGは画像を小さな正方形に分割し、画像の一部が動く場合、動きと変化だけが明るさで保存されます。25fpsムービーでは、フレーム間の多くの変更は非常にわずかです。繰り返しますが、デルタエンコーディング+圧縮。静的なシーンに最適です。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.