델타 압축은 네트워크를 통해 전송되는 데이터의 양을 어떻게 줄입니까?


26

많은 게임에서 전송 된 데이터로드를 낮추기 위해 델타 압축 기술을 사용합니다. 이 기술이 실제로 데이터로드를 어떻게 낮추는 지 이해하지 못합니까?

예를 들어 포지션을 보내고 싶다고 가정 해 봅시다. 델타 압축이 없으면 vector3예를 들어 엔티티의 정확한 위치로을 보냅니다 (30, 2, 19). 델타 압축을 사용하면 vector3더 작은 숫자를 보냅니다 (0.2, 0.1, 0.04).

두 메시지가 vector3각 부동 소수점에 대해 32 비트-32 * 3 = 96 비트 인 경우 데이터로드를 낮추는 방법을 이해하지 못합니다 !

각 부동 소수점을 바이트로 변환 한 다음 바이트에서 부동 소수점으로 다시 변환 할 수 있지만 정밀도 오류가 발생한다는 것을 알고 있습니다.


모든 형태의 델타 압축을 사용하는 게임과 네트워킹을 할 때마다 완벽하게 결정적이어야합니다 (실패하고 비 동기화 됨). "바이트에서 부동으로 변환"(또는 무엇이든)하여 정밀한 오류가 발생하는지 여부는 중요하지 않습니다. 필요한 조건은 모든 동기화 된 컴퓨터 / 게임에서 모든 것이 정확히 동일해야합니다. "정밀도 오류"가있는 경우 (풀 플로트를 사용하더라도 피할 수 없습니다-CPU가 내 CPU와 동일한 플로트를 사용하지 않음) 모든 컴퓨터에서 동일해야하므로 표시 되지 않습니다 . 눈에 보이는 효과를 피하기 위해 유형을 선택했습니다.
Luaan

2
@Luaan 또는 가끔씩 부분 절대 상태를 추가 할 수 있습니다. 예를 들어, 항상 몇 개의 엔티티를 선택하고 절대 위치를 전달할 수 있으며 플레이어와 가까운 엔티티를 선택하는 것을 선호합니다.
ratchet freak

어떻게 든, 나는이 질문이 rsync의 상대적인 상대에 대해 기대하고 있었다 ...
SamB

허프만 코딩.
벤 Voigt

중간 사람을 사용하십시오
John Demetriou

답변:


41

저장된 멀티 플레이어 게임의로드시 또는 재 동기화가 필요한 경우와 같이 전체 게임 상태를 전송하지 않는 경우가 있습니다. 그러나 전체 상태 전송은 일반적으로 피할 수 있으며 델타 인코딩 이 사용 되는 곳 입니다. 일반적으로 델타 압축이 전부 입니다. 귀하의 예는 실제로 그러한 상황을 설명하지 않습니다. 델타 압축이 언급되는 이유는 순진한 구현이 종종 순진한 게임 구현이 저장하는 것이므로 순진한 구현은 종종 델타 보다는 상태를 전송 하기 때문입니다. 그런 다음 델타는 최적화입니다.

델타를 사용 하면 이동하지 않은 유닛의 위치를 ​​전혀 보내지 않습니다 . 그것이 바로 그 정신입니다.

우리가 수년간 펜팔이었다고 상상하며 기억을 잃어 버렸습니다 (그리고 읽은 후에 모든 편지를 버렸습니다). 단순히 일련의 편지를 평소처럼 계속 사용하는 대신 인생의 전체 역사를 하나의 거대한 편지로 다시 써야합니다.

주어진 경우 전체 코드를 보내는 데 필요한 큰 비트 범위와 달리 델타를 인코딩하는 데 더 적은 수의 비트를 사용하는 것이 가능할 수도 있습니다 (코드베이스에 따라 다름). 세계가 수 킬로미터에 달한다고 가정하면 주어진 축에서 센티미터로 정확하게 위치를 인코딩하려면 32 비트 부동 소수점이 필요할 수 있습니다. 그러나 틱당 몇 미터에 불과한 엔티티에 적용 할 수있는 최대 속도를 고려할 때 8 비트 (2 ^ 8 = 256이므로 최대 200cm를 저장하기에 충분) 만 가능합니다. 물론 고정 소수점 번거 로움을 원하지 않으면 OpenGL에서와 같이 부동 소수점 사용법 또는 일부 반 / 1/4 플로트 대신 고정 된 것으로 가정합니다.


7
당신의 대답은 명확하지 않습니다. 움직이지 않는 객체의 정보를 보내지 않는 것은 델타 인코딩의 부작용 일 뿐이며 "그 정신"이 아닙니다. 델타 인코딩을 사용하는 실제 이유는 래칫 괴물의 대답에서 더 잘 강조 표시되는 것 같습니다.
Etsitpab Nioliv

14
@EtsitpabNioliv "The Spirit"은 단순히 "필요하지 않은 것을 보내지 마십시오"입니다. 이것은 비트 레벨로 내려갈 수 있습니다. "전선을 통해 필요한 메시지를 얻는 데 필요한만큼의 대역폭 만 사용하십시오". 이 답변은 다른 사람들에게 충분히 분명한 것 같습니다. 감사.
엔지니어

4
@EtsitpabNioliv SVN이 파일을 서버 측에 저장하는 방법에 대해 배운 적이 있습니까? 커밋마다 전체 파일을 저장하지는 않습니다. 델타 , 각 커밋에 포함 된 변경 사항을 저장 합니다. "델타"라는 용어는 종종 수학과 프로그래밍에서 두 값 의 차이 를 나타내는 데 사용 됩니다. 저는 게임 프로그래머는 아니지만 게임에서 사용법이 많이 다르다면 놀랄 것입니다. 그러면 모든 것이 아니라 차이점 만 보내서 보내야하는 데이터의 양을 "압축"해야합니다. (
그중

1
또한 작은 숫자는 0 비트 수가 많으며, 적절한 압축 알고리즘을 사용하여 전송 된 정보를 인코딩 / 디코딩하면 네트워크를 통해 전송되는 페이로드가 더 작아 질 수 있습니다.
liggiorgio

22

델타가 잘못되었습니다. 개별 요소의 델타를보고 있습니다. 전체 장면의 델타를 생각해야합니다.

장면에 100 개의 요소가 있지만 그 중 하나만 이동했다고 가정합니다. 100 개의 요소 벡터를 보내면 99 개가 낭비됩니다. 정말로 1 만 보내면됩니다.

이제 모든 요소 벡터를 저장하는 JSON 객체가 있다고 가정 해 봅시다. 이 객체는 서버와 클라이언트간에 동기화됩니다. "그렇게 그렇게 움직였습니까?"를 결정하는 대신 JSON 객체에서 다음 게임 틱을 생성 diff tick100.json tick101.json하고 diff를 보내고 보낼 수 있습니다. 클라이언트 쪽에서 현재 눈금의 벡터에 diff를 적용하면 모든 설정이 완료됩니다.

이렇게하면 텍스트 (또는 이진)의 차이를 감지하는 데있어 수십 년의 전문 지식을 활용하여 자신이없는 것에 대해 걱정할 필요가 없습니다. 이제 이상적으로 개발자가 쉽게 사용할 수 있도록 배후에서이 작업을 수행하는 라이브러리를 사용하십시오.


2
diff비효율적 인 핵과 같은 소리를 사용 합니다. 수신쪽에 JSON 문자열을 유지하고 매번 패치하고 역 직렬화 할 필요가 없습니다. 두 키-값 사전의 차이를 계산하는 것은 복잡한 작업이 아닙니다. 기본적으로 모든 키를 반복하고 값이 같은지 확인하면됩니다. 그렇지 않은 경우 결과 키-값 dict에 추가하고 마지막으로 dict 직렬화를 JSON으로 보냅니다. 단순하고 수년간의 전문 지식이 필요하지 않습니다. diff와 달리이 방법은 1) 기존 (대체 된) 데이터를 포함하지 않습니다. 2) UDP로 더 잘 재생됩니다. 3) 개행에 의존하지 않습니다
gronostaj

8
@gronostaj 이것은 요점을 알 수있는 예입니다. 나는 실제로 JSON에 diff를 사용하는 것을 옹호하지 않습니다. 그것이 "suppose"라고 말하는 이유입니다.
corsiKa

2
"이 작업을 수행하면 텍스트 (또는 이진)의 차이를 감지하는 데 수십 년간의 전문 지식을 활용하고 자신을 잃어 버릴 염려가 없습니다. 이제 이상적으로도 배경 뒤에서이를 수행하는 라이브러리를 사용하는 것이 이상적입니다. 개발자로서 더 쉬워졌습니다. " 그 부분은 분명히 diff 사용을 제안하거나 아무도 합리적으로 그런 일을하지 않을 때 diff를 사용하는 라이브러리를 사용하는 것처럼 들립니다. 나는 "diffing의"델타 압축을 호출 할 것이다, 그냥 델타 압축의, 유사성은 피상적
Selali Adobor

최적의 diff와 최적의 델타 압축은 동일합니다. 명령 행의 diff 유틸리티는 텍스트 파일에 적합하며 최적의 결과를 제공하지는 않지만 델타 압축을 수행하는 라이브러리를 조사하는 것이 좋습니다. 이런 의미에서 delta-delta와 diff라는 단어는 문자 그대로 같은 의미입니다. 그것은 몇 년 동안 잃어버린 것 같습니다.
corsiKa

9

종종 다른 압축 메커니즘은 예를 들어 산술 압축과 같은 델타 인코딩과 함께 사용됩니다.

이러한 압축 방식은 가능한 값을 예측 가능하게 그룹화 할 때 훨씬 잘 작동합니다. 델타 인코딩은 0 주위의 값을 그룹화합니다.


1
또 다른 예 : 100 우주선 자신의 위치에 각하지만 같은 속도 벡터로 여행이있는 경우에만 속도를 보낼 필요가 번 (또는 적어도 그것은 압축이 정말 잘); 그렇지 않으면 대신 100 개의 포지션을 보내야합니다. 다른 사람들이 더하게하십시오. 공유 상태 잠금 단계 시뮬레이션을 공격적인 델타 압축 형식으로 생각하면 속도를 보내지 않고 플레이어에서 오는 명령 만 보냅니다. 다시 한 번, 모두 자신의 추가 작업을 수행하십시오.
Luaan

동의한다. 압축은 답과 관련이 있습니다.
Leopoldo Sanczyk

8

당신은 전반적으로 정확하지만 한 가지 중요한 요점이 없습니다.

게임의 엔티티는 여러 속성으로 설명되며 위치는 하나 입니다.

어떤 종류의 속성? 네트워크 게임에서 너무 열심히 생각하지 않고도 다음을 포함 할 수 있습니다.

  • 위치.
  • 정위.
  • 현재 프레임 번호
  • 색상 / 조명 정보.
  • 투명도.
  • 사용할 모델.
  • 사용할 질감.
  • 특수 효과.
  • 기타.

이들을 각각 따로 선택하면 주어진 프레임에서 변경 해야하는 경우 전체를 다시 전송해야합니다.

그러나 이러한 속성이 모두 같은 비율로 변경되는 것은 아닙니다 .

모델이 바뀌지 않습니까? 델타 압축이 없으면 어쨌든 다시 전송해야합니다. 델타 컴파일을 사용하면 필요하지 않습니다.

위치와 방향은 더 흥미로운 두 가지 경우로, 일반적으로 각각 3 개의 플로트로 구성됩니다. 임의의 주어진 두 프레임 사이에서, 각각의 3 개의 플로트 세트 중 1 개 또는 2 개만이 변할 가능성이있다. 직선으로 움직입니까? 회전하지 않습니까? 점프하지 않습니까? 델타 압축이 없으면 전체를 다시 전송해야하지만 델타 압축을 사용하면 변경되는 내용 만 다시 전송하면됩니다.


8

나이브 델타 계산 자체는 피연산자와 동일한 크기의 데이터 구조에 저장되고 추가 처리없이 전송되면 트래픽을 저장하지 않습니다.

그러나 델타 기반으로 잘 설계된 시스템이 트래픽을 절약 할 수있는 두 가지 방법이 있습니다.

첫째, 많은 경우 델타는 0이됩니다. 델타가 0이면 전혀 보내지 않도록 프로토콜을 설계 할 수 있습니다. 분명히, 당신이 무엇을 보내거나 보내지 않았는지 표시해야하기 때문에 약간의 오버 헤드가 있지만 전반적으로 큰 승리 일 것입니다.

둘째, 델타는 일반적으로 원래 숫자보다 훨씬 작은 값 범위를 갖습니다. 그 범위는 0을 중심으로합니다. 이는 대부분의 델타에 대해 더 작은 데이터 유형을 사용하거나 범용 압축 알고리즘을 통해 전체 데이터 스트림을 전달하여 악용 될 수 있습니다.


6

델타 인코딩이 변경 사항을 전체 상태로만 보내는 방법에 대한 대부분의 답변에 대해 이야기하지만 전체 상태에서 압축해야하는 데이터 양을 줄이기위한 필터로 사용할 수있는 "델타 인코딩"이라는 또 다른 방법이 있습니다. 질문에 혼란이 생길 ​​수있는 곳이기도합니다.

숫자로 구성된 벡터를 인코딩 할 때 일부 경우 (예 : 정수, 열거 형 등) 개별 요소에 대해 가변 바이트 인코딩을 사용할 수 있으며,이 경우에는 각 요소에 필요한 데이터 양을 더 줄일 수 있습니다 누적 합계 또는 최소값과 각 값과 최소값의 차이로 저장하는 경우

예를 들어 벡터 {68923, 69012, 69013, 69015}를 인코딩하려면을로 델타 인코딩 할 수 있습니다 {68923, 89, 1, 2}. 바이트 당 7 비트의 데이터를 저장하고 1 비트를 사용하여 다른 바이트가 오는 것을 나타내는 간단한 가변 바이트 인코딩을 사용하면 배열의 각 개별 요소가이를 전송하려면 3 바이트가 필요하지만 델타 인코딩 버전 첫 번째 요소에는 3 바이트 만, 나머지 요소에는 1 바이트 만 필요합니다. 직렬화하는 데이터의 종류에 따라 이것은 상당히 인상적인 절약으로 이어질 수 있습니다.

그러나 이것은 직렬화 최적화에 대한 것이지, 게임 상태 (또는 비디오 등)의 일부로서 임의의 데이터를 스트리밍 할 때 "델타 인코딩"에 대해 이야기 할 때 일반적으로 의미하는 것은 아닙니다. 다른 답변은 이미 그것을 설명하는 적절한 작업을 수행합니다.


4

압축 알고리즘이 diff에서 더 잘 작동한다는 점도 주목할 가치가 있습니다. 다른 답변에서 언급했듯이 대부분의 요소는 두 상태 사이에서 동일하게 유지되거나 값이 조금씩 변경됩니다. 두 경우 모두 압축 벡터를 숫자 벡터의 차이에 적용하면 크게 절약 할 수 있습니다. 당신이 경우에도 하지 않는 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

좋은 예입니다. 압축은 여기서 중요한 역할을합니다.
Leopoldo Sanczyk

0

위치가 vector3에 저장되어 있지만 실제 엔티티는 한 번에 몇 개의 정수만 이동할 수 있습니다. 그런 다음 델타를 바이트 단위로 보내면 정수로 보내는 것보다 낫습니다.

현재 위치 : 23009.0, 1.0, 2342.0 (3 float)
새로운 위치 : 23010.0, 1.0, 2341.0 (3 float)
델타 : 1, 0, -1 (3 byte)

매번 정확한 위치를 전송하는 대신 델타를 전송합니다.


0

델타 압축은 델타 인코딩 된 값의 압축입니다. 델타 인코딩은 숫자의 다른 통계 분포를 생성하는 변환입니다. 분포가 선택한 압축 알고리즘에 유리하면 데이터 양이 줄어 듭니다. 엔티티가 두 업데이트 사이에서 약간만 움직이는 게임과 같은 시스템에서 매우 잘 작동합니다.

2D에 100 개의 엔티티가 있다고 가정 해 봅시다. 큰 그리드에서 512 x 512. 예제를 위해 정수만 고려하십시오. 엔터티 당 두 개의 정수 또는 200 개의 숫자입니다.

두 업데이트 사이에서 모든 위치는 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 개의 고유 숫자 (모두 Y가 다른 동일한 X에 있음)입니다. 그것은 숫자 당 적어도 7 비트 (175 바이트)이며 압축 알고리즘에는 매우 어렵습니다.

델타 압축은 엔티티가 약간만 변경되는 특수한 경우에 작동합니다. 고유 한 변경 사항이 많으면 델타 인코딩이 도움이되지 않습니다.


델타 인코딩 및 압축은 다른 상황에서도 다른 변환과 함께 사용됩니다.

MPEG은 사진을 작은 사각형으로 분할하고 사진의 일부가 움직이면 움직임과 변경 내용의 밝기 만 저장됩니다. 25fps 동영상에서 프레임 사이의 많은 변화는 매우 작습니다. 다시 델타 인코딩 + 압축. 정적 장면에 가장 적합합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.