Як стиснення дельти зменшує кількість даних, що надсилаються по мережі?


26

У багатьох іграх використовується техніка дельта-стиснення, щоб знизити надіслане навантаження даних. Я не розумію, як ця методика насправді знижує навантаження даних?

Наприклад, скажімо, що я хочу надіслати позицію. Без дельта стиснення, наприклад, я надсилаю vector3точне положення сутності (30, 2, 19). При стисненні дельти я надсилаю vector3менші числа (0.2, 0.1, 0.04).

Я не розумію, як це знижує навантаження даних, якщо обидва повідомлення vector3- 32 біти для кожного плаву - 32 * 3 = 96 біт!

Я знаю, що ви можете перетворити кожен поплавок у байт, а потім перетворити його назад з байта в плаваючий, але це спричиняє видимі помилки точності.


Будь-який час, коли ви займаєтесь мережею з грою, яка використовує будь-яку форму стиснення дельти, вам потрібно бути досконало детермінованим (не виходить з ладу, і ви отримуєте дезинки). Незалежно від того, чи "перетворення байта на плаваючий" (чи будь-що інше) викликає помилки точності, не важливо - необхідною умовою є все точно так само у всіх синхронізованих машинах / іграх. Якщо є "помилки точності" (неминучі, навіть якщо ви використовували повні плавки - ваш процесор не використовує ті ж плавці, що і мій процесор), вони повинні бути однаковими на всіх машинах - і тому вони не видно. Ви вибрали типи, щоб уникнути видимих ​​ефектів.
Луань

2
@Luaan або ви можете додавати частковий абсолютний стан так часто, наприклад, ви можете так часто вибирати кілька сутностей і передавати абсолютну позицію, віддаючи перевагу вибору об'єктів, близьких до гравця.
храповик виродка

Якось я очікував, що це питання стосується якогось родича rsync ...
SamB

Хаффман Кодування.
Бен Войгт

Просто використовуйте середнього чоловіка
Джона Деметріу

Відповіді:


41

Бувають випадки, коли ви не можете уникнути надсилання повного стану гри - наприклад, при завантаженні збереженої багатокористувацької гри або коли потрібна повторна синхронізація. Але надсилати повний стан, як правило, можна уникнути, і тут відбувається дельта кодування . Взагалі, це все, про що відбувається дельта стиснення; ваш приклад насправді не описує цю ситуацію. Причина стиснення дельти навіть згадується через те, що наївні реалізації часто надсилають стан, а не дельти, тому що стан - це те, що зберігається в будь-якій наївній грі. Дельти тоді є оптимізацією.

З дельтами ви ніколи не надсилатимете позиції одиниць, які не рухалися . У цьому дух.

Уявіть, що ми були друзями роками, і я втратив пам’ять (і відкинув усі ваші листи, прочитавши їх). Замість того, щоб просто продовжувати свою серію листів як звичайно, вам доведеться написати мені всю історію свого життя одним масивним листом, і знову, щоб я наздогнав.

У вашому випадку, можливо, (залежно від вашої бази даних коду) можливо використовувати меншу кількість біт для кодування дельт, на відміну від великих бітових діапазонів, необхідних для надсилання повного стану. Скажімо, світ налічує багато кілометрів, можливо, вам знадобиться 32-бітний поплавок, щоб точно кодувати позиції вниз, скажімо, сантиметр на даній осі. Однак, враховуючи максимальну швидкість, застосовну до сутностей, яка може становити лише пару метрів на галочку, це може бути здійснено всього в 8 біт (2 ^ 8 = 256, достатньо для зберігання максимуму 200 см). Звичайно, це передбачає фіксовану, а не використання з плаваючою точкою ... або якесь плавання на пів / чверть, як у OpenGL, якщо ви не хочете проблем з фіксованою точкою.


7
Ваша відповідь мені не зрозуміла. Я відчуваю, що не надсилання інформації про рухомий об’єкт - це лише побічний ефект кодування дельти, а не "Дух цього". Справжня причина використання кодування дельти, схоже, краще висвітлена у відповіді щурячого виродка.
Етсітпаб Ніолів

14
@EtsitpabNioliv "Дух" - це просто "не надсилати те, чого не потрібно". Це можна зняти до рівня бітів - "використовуйте лише стільки пропускної здатності, скільки вам потрібно, щоб отримати необхідне повідомлення по дроту". Ця відповідь очевидно здається достатньо зрозумілою для всіх інших. Спасибі.
Інженер

4
@EtsitpabNioliv Коли-небудь дізналися про те, як SVN зберігає файли на сервері? Він не зберігає весь файл кожного комітету. Він зберігає дельти , зміни, які містить кожен комітет. Термін "дельта" часто використовується в математиці та програмуванні для позначення різниці між двома значеннями. Я не ігровий програміст, але буду здивований, якщо використання в іграх так сильно відрізняється. Тоді ідея має сенс: ви «стискаєте» кількість даних, які ви повинні надіслати, надсилаючи лише різниці, а не все. (Якщо якась частина мене бентежить, це слово "стиснути".)
jpmc26

1
Крім того, невеликі числа мають більшу кількість нульових бітів, і використання відповідного алгоритму стиснення для кодування / декодування надісланої інформації може призвести до ще меншої корисної навантаження для надсилання через мережу.
liggiorgio

22

У вас неправильна дельта. Ви дивитесь на дельту окремих елементів. Потрібно продумати дельту всієї сцени.

Припустимо, у вас на сцені 100 елементів, але лише 1 з них перемістився. Якщо надіслати 100 векторів елементів, 99 з них витрачаються даремно. Вам дійсно потрібно лише надіслати 1.

Скажімо, у вас є об'єкт JSON, який зберігає всі ваші елементи векторів. Цей об'єкт синхронізований між вашим сервером та вашим клієнтом. Замість того, щоб вирішити, "зробив так і так рухатися?" ви можете просто генерувати свою наступну ігрову галочку в об'єкті JSON, зробітьdiff tick100.json tick101.json і надіслати цей розл. На стороні клієнта ви застосовуєте diff до вектора вашого поточного галочки і все готово.

Роблячи це, ви використовуєте десятиліття досвіду виявлення відмінностей у тексті (або двійковому!) І не потрібно турбуватися про те, щоб нічого не пропустити. Тепер в ідеалі ви також використовуєте бібліотеку, яка робить це за кадром для вас, щоб зробити це ще простіше для вас як розробника.


2
Використання diffзвуків як неефективний злом. Утримувати рядок JSON на приймальному кінці, виправляти та десеріалізувати її кожен раз непотрібно. Обчислення різниці двох словників ключових значень не є складним завданням, в основному ви просто перебираєте всі клавіші і перевіряєте, чи значення рівні. Якщо ні, ви додаєте їх до результуючого ключового значення і, нарешті, ви надсилаєте дікта серіалізованим до JSON. Просто, не потрібні роки досвіду. На відміну від різниці, цей метод: 1) не буде містити старих (замінених) даних; 2) прекрасніше грає з UDP; 3) не покладається на нові рядки
gronostaj

8
@gronostaj Це був приклад, щоб зрозуміти кращу точку. Я насправді не виступаю за використання diff для JSON - саме тому скажіть "припустимо".
corsiKa

2
"Ви робите це, ви використовуєте десятиліття досвіду виявлення відмінностей у тексті (або двійковому!) І не потрібно турбуватися про те, щоб нічого не пропустити. Тепер в ідеалі ви також використовуєте бібліотеку, яка робить це за кадром для вас, щоб зробити це рівним простіше вам як розробнику ". Ця частина, безумовно, звучить так, ніби ви пропонуєте використовувати diff або використовувати бібліотеку, яка використовує diff, коли ніхто розумно не зробить такого. Я б не назвав дельта стиснення "різним", це просто дельта стиснення, подібності є поверхневими
Selali Adobor

Оптимальна диференція та оптимальне дельта стиснення однакові. Хоча утиліта diff у командному рядку орієнтована на текстові файли і, ймовірно, не забезпечить оптимального результату, я рекомендую досліджувати бібліотеки, які роблять дельта стиснення для вас. Немає нічого фантазійного в питанні слова delta - delta і diff, в цьому сенсі, означають буквально те саме. Це, здається, втрачено за ці роки.
corsiKa

9

Дуже часто інший механізм стиснення буде поєднуватися з дельта-кодуванням, як, наприклад, арифметичне стиснення.

Ці схеми стиснення працюють набагато краще, коли можливі значення групуються передбачувано. Delta кодування групуватиме значення навколо 0.


1
Інший приклад: якщо у вас 100 космічних кораблів, кожен має своє положення, але рухаєтесь з тим самим вектором швидкості, вам потрібно надіслати швидкість лише один раз (або принаймні примусити його стиснутись справді ); в іншому випадку вам потрібно буде надіслати 100 позицій замість цього. Нехай інші роблять додавання. Якщо ви вважаєте симулятори блокування в режимі загального стану агресивною формою дельта-стиснення, ви навіть не надсилаєте швидкості - лише команди, що надходять від програвача. Знову ж таки, нехай кожен робить власне додавання.
Луань

Я згоден. Стиснення доречне у відповіді.
Леопольдо Санчик

8

Ви в цілому правильні, але пропускаєте один важливий момент.

Суб'єкти в грі описуються багатьма атрибутами, позиція яких є лише однією .

Що за атрибути? Без того, щоб думати занадто важко, в мережевій грі це може бути:

  • Позиція.
  • Орієнтація.
  • Поточний номер кадру
  • Інформація про колір / освітлення.
  • Прозорість.
  • Модель для використання.
  • Текстура для використання.
  • Спецефекти.
  • І т.д.

Якщо ви вибираєте кожне із них окремо, ви, безумовно, можете зробити так, що якщо в будь-якому заданому кадрі його потрібно змінити, він повинен бути повторно переданий у повному обсязі.

Однак не всі ці ознаки змінюються однаковою швидкістю .

Модель не змінюється? Без стиснення дельти його потрібно будь-коли повторно передавати. При дельтальному стисненні цього не повинно бути.

Положення та орієнтація - це два цікавіші випадки, які зазвичай складаються з 3 поплавків у кожному. Між будь-якими заданими двома кадрами, існує можливість, що лише 1 або 2 з кожного набору з 3 плавців можуть змінюватися. Переміщення по прямій? Не обертається? Ніяких стрибків? Це всі випадки, коли без дельтаного стиснення потрібно повторно передавати в повному обсязі, але при стисненні дельти потрібно лише повторно передавати те, що змінюється.


8

Ви маєте рацію, що наївний розрахунок дельти самостійно, при цьому результат зберігається в тій же структурі даних, що й операнди та передається без подальшої обробки, не економить жоден трафік.

Однак, є два способи добре спроектована система, заснована на дельтах, дозволяє заощадити трафік.

По-перше, у багатьох випадках дельта буде дорівнює нулю. Ви можете спроектувати свій протокол таким чином, що якщо дельта дорівнює нулю, ви його взагалі не надсилаєте. Очевидно, що це є деякими накладними витратами на це, оскільки вам потрібно вказати, що ви є чи не надсилаєте, але в цілому це може бути великий виграш.

По-друге, дельти зазвичай матимуть значно менший діапазон значень, ніж початкові числа. і цей діапазон буде зосереджений на нулі. Це можна використати або за допомогою меншого типу даних для більшості дельт, або шляхом передачі повного потоку даних через алгоритм стиснення загального призначення.


6

Хоча більшість відповідей говорять про те, як кодування дельти стосується лише надсилання змін до стану в цілому, є ще одна річ, яка називається «дельта кодування», яка може бути використана як фільтр для зменшення кількості даних, які потрібно стиснути в повному стані також оновлення, яке може виникнути, коли виникає плутанина у запитанні.

Кодуючи вектор чисел, ви можете в деяких випадках (наприклад, цілі числа, перерахунки тощо) використовувати кодування змінних байтів для окремих елементів, а в деяких з цих випадків ви можете додатково зменшити кількість даних, необхідний кожному елементу якщо ви зберігаєте його як або запущені суми, або як мінімальне значення та різницю між кожним значенням і цим мінімумом.

Наприклад, якщо ви хочете кодувати вектор {68923, 69012, 69013, 69015} ви можете дельта-кодувати це як {68923, 89, 1, 2}. Використовуючи тривіальне кодування змінних байтів, де ви зберігаєте 7 біт даних на байт і використовуєте один біт, щоб вказати, що надходить інший байт, кожному з окремих елементів масиву буде потрібно 3 байти, щоб передати його, але версія, кодована дельтою знадобиться лише 3 байти для першого елемента і 1 байт для решти елементів; залежно від типу даних, які ви серіалізуєте, це може призвести до значних економій.

Однак це скоріше оптимізація серіалізації та не є тим, що взагалі мається на увазі, коли ми говоримо про «кодування дельти», коли мова йде про потокове передавання довільних даних у складі ігрового стану (або відео чи подібного); інші відповіді вже роблять адекватну роботу з пояснення цього.


4

Варто також зазначити, що алгоритми стиснення краще виконують свою роботу на відмінності. В інших відповідях згадується, що або більшість ваших елементів залишаються однаковими між двома станами, або значення змінюються невеликим дробом. В обох цих випадках застосування алгоритму стиснення до різниці вашого вектора чисел дає значну економію. Навіть якщо ви не застосуєте до вектора зайвої логіки, як-от видалення 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 float)
Нова позиція: 23010.0, 1.0, 2341.0 (3 float)
Delta: 1, 0, -1 (3 byte)

Замість того, щоб щоразу надсилати точну позицію, ми надсилаємо дельту.


0

Дельта стиснення - це стиснення дельта-кодованих значень. Дельта кодування - це перетворення, яке виробляє різний статистичний розподіл чисел. Якщо розподіл сприятливий для обраного алгоритму стиснення, він знижує кількість даних. Він дуже добре працює в такій системі, як гра, в якій суттєво переміщаються між двома оновленнями.

Скажімо, у вас є 100 сутностей у 2D. На великій сітці, 512 x 512. Розглянемо лише цілі числа заради прикладу. Це два цілих числа на сутність або 200 чисел.

Між двома оновленнями всі наші позиції змінюються або на 0, 1, -1, 2 або -2. Зафіксовано 100 випадків 0, 33 випадки 1 і -1 і лише 17 випадків 2 і -2. Це досить поширене явище. Ми вибираємо кодування Хаффмана для стиснення.

Дерево Хаффмана для цього буде:

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

Усі ваші 0 будуть закодовані як один біт. Це всього 100 біт. 66 значень будуть закодовані у вигляді 3 біт, а лише 34 значення - 4 біти. Це 434 біти або 55 байт. Плюс кілька невеликих накладних, щоб врятувати наше дерево картографування, оскільки дерево крихітне. Зауважте, що для кодування 5 чисел потрібні 3 біти. Тут ми торгували можливістю використовувати 1 біт для '0' для необхідності використання 4 біт для '-2'.

Тепер порівняйте це з надсиланням 200 довільних чисел. Якщо ваші особи не можуть бути на одній плитці, ви майже гарантовано отримаєте неправильний статистичний розподіл. Найкращим випадком буде 100 унікальних чисел (всі на одному X з різним Y). Це щонайменше 7 біт на число (175 байт) і дуже важко для будь-якого алгоритму стиснення.

Дельта стиснення працює в особливому випадку, коли ваші сутності змінюються лише незначно. Якщо у вас багато унікальних змін, кодування дельти не допоможе.


Зауважте, що кодування дельта та стиснення застосовується і в інших ситуаціях з іншими перетвореннями.

MPEG розбиває зображення на невеликі квадрати, і якщо частина зображення переміщується, зберігаються лише рух і зміна яскравості. У фільмі з 25 кадрів в секунду дуже багато змін між кадрами дуже мало. Знову-таки дельта-кодування + стиснення. Найкраще працює для статичних сцен.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.