Làm thế nào để nén delta làm giảm lượng dữ liệu được gửi qua mạng?


26

Nhiều trò chơi sử dụng kỹ thuật nén delta để giảm tải dữ liệu được gửi. Tôi không hiểu làm thế nào kỹ thuật này thực sự làm giảm tải dữ liệu?

Ví dụ: giả sử tôi muốn gửi một vị trí. vector3Ví dụ, không có nén delta, tôi gửi một vị trí chính xác của thực thể (30, 2, 19). Với nén delta tôi gửi a vector3với số lượng nhỏ hơn (0.2, 0.1, 0.04).

Tôi không hiểu làm thế nào nó giảm tải dữ liệu nếu cả hai thông báo là vector3- 32 bit cho mỗi lần thả - 32 * 3 = 96 bit!

Tôi biết bạn có thể chuyển đổi từng float thành một byte và sau đó chuyển đổi lại từ byte sang float nhưng nó gây ra các lỗi chính xác có thể nhìn thấy.


Bất cứ khi nào bạn thực hiện kết nối với một trò chơi sử dụng bất kỳ hình thức nén delta nào, bạn cần phải có tính xác định hoàn hảo (thất bại và bạn nhận được desyncs). Cho dù "chuyển đổi từ byte sang float" (hoặc bất cứ điều gì) gây ra lỗi chính xác không quan trọng - điều kiện cần là có mọi thứ giống hệt nhau trong tất cả các máy / trò chơi được đồng bộ hóa. Nếu có "lỗi chính xác" (không thể tránh khỏi, ngay cả khi bạn đã sử dụng toàn bộ số float - CPU của bạn không sử dụng cùng số float như CPU ​​của tôi), chúng cần phải giống nhau trên tất cả các máy - và do đó không thể nhìn thấy. Bạn đã chọn các loại để tránh các hiệu ứng có thể nhìn thấy.
Luaan

2
@Luaan hoặc bạn có thể thêm trạng thái tuyệt đối một phần thường xuyên, ví dụ bạn có thể thường xuyên chọn một vài thực thể và vượt qua vị trí tuyệt đối, thích chọn các thực thể gần với trình phát.
quái vật ratchet

Bằng cách nào đó, tôi đã mong đợi câu hỏi này là về một số người thân của rsync ...
SamB

Mã hóa Huffman.
Ben Voigt

Chỉ cần sử dụng người đàn ông trung lưu
John Demetriou

Câu trả lời:


41

Đôi khi bạn không thể tránh gửi trạng thái trò chơi đầy đủ - chẳng hạn như khi tải trò chơi nhiều người chơi đã lưu hoặc khi cần đồng bộ lại. Nhưng việc gửi trạng thái đầy đủ thường là có thể tránh được và đó là nơi mã hóa delta xuất hiện. Nói chung, đây là tất cả những gì về nén delta; ví dụ của bạn không thực sự mô tả tình huống đó. Lý do nén delta thậm chí được đề cập là bởi vì các triển khai ngây thơ thường sẽ gửi trạng thái chứ không phải deltas vì trạng thái thường là bất kỳ cửa hàng triển khai trò chơi ngây thơ nào. Deltas sau đó là một tối ưu hóa.

Với đồng bằng, bạn sẽ không bao giờ gửi vị trí của các đơn vị không di chuyển , tất cả. Đó là tinh thần của nó.

Hãy tưởng tượng chúng tôi là penpals trong nhiều năm và tôi bị mất trí nhớ (và đã loại bỏ tất cả các chữ cái của bạn sau khi đọc chúng). Thay vì chỉ tiếp tục với hàng loạt thư của bạn như bình thường, bạn sẽ phải viết cho tôi toàn bộ lịch sử cuộc sống của bạn bằng một lá thư lớn, một lần nữa, để tôi bắt kịp.

Trong trường hợp cụ thể của bạn, có thể (tùy thuộc vào cơ sở mã của bạn) có thể sử dụng số bit thấp hơn để mã hóa vùng đồng bằng, trái ngược với phạm vi bit lớn cần thiết để gửi trạng thái đầy đủ. Giả sử thế giới có nhiều km, bạn có thể cần phao 32 bit để mã hóa chính xác các vị trí xuống để nói, một centimet trong một trục nhất định. Tuy nhiên, với tốc độ tối đa áp dụng cho các thực thể, có thể chỉ là vài mét trên mỗi tích tắc, có thể thực hiện được chỉ trong 8 bit (2 ^ 8 = 256 đủ để lưu trữ tối đa 200cm). Tất nhiên, điều này giả định cố định thay vì sử dụng dấu phẩy động ... hoặc một số loại nổi nửa / quý như trong OpenGL, nếu bạn không muốn các rắc rối điểm cố định.


7
Câu trả lời của bạn không rõ ràng với tôi. Tôi cảm thấy việc không gửi thông tin của một đối tượng không chuyển động chỉ là tác dụng phụ của mã hóa delta và không phải là "Tinh thần của nó". Lý do thực sự để sử dụng mã hóa delta dường như được làm nổi bật trong câu trả lời của ratchet freak tốt hơn.
Etsitpab Nioliv

14
@EtsitpabNioliv "The Spirit" chỉ đơn giản là "đừng gửi những gì bạn không phải". Điều này có thể được đưa xuống mức bit - "chỉ sử dụng băng thông nhiều như bạn cần để nhận thông điệp cần thiết qua dây". Câu trả lời này rõ ràng có vẻ đủ rõ ràng cho những người khác. Cảm ơn.
Kỹ sư

4
@EtsitpabNioliv Bạn đã bao giờ tìm hiểu về cách SVN lưu trữ phía máy chủ tệp? Nó không lưu trữ toàn bộ tập tin mỗi cam kết. Nó lưu trữ deltas , những thay đổi mỗi cam kết chứa. Thuật ngữ "delta" thường được sử dụng trong toán học và lập trình để chỉ sự khác biệt giữa hai giá trị. Tôi không phải là một lập trình viên trò chơi, nhưng tôi sẽ ngạc nhiên nếu cách sử dụng khác nhau nhiều trong chơi game. Ý tưởng sau đó có ý nghĩa: bạn "nén" lượng dữ liệu bạn phải gửi bằng cách chỉ gửi những khác biệt, thay vì mọi thứ. (Nếu bất kỳ phần nào của nó gây nhầm lẫn với tôi, thì đó là từ "nén.")
jpmc26

1
Ngoài ra, các số nhỏ có số bit bằng 0 cao hơn và sử dụng thuật toán nén phù hợp để mã hóa / giải mã thông tin được gửi có thể dẫn đến tải trọng nhỏ hơn được gửi qua mạng.
liggiorgio

22

Bạn có đồng bằng sai. Bạn đang nhìn vào vùng đồng bằng của các yếu tố riêng lẻ. Bạn cần phải nghĩ về đồng bằng của toàn bộ cảnh.

Giả sử bạn có 100 yếu tố trong cảnh của mình, nhưng chỉ có 1 trong số chúng di chuyển. Nếu bạn gửi 100 vectơ phần tử, 99 trong số chúng bị lãng phí. Bạn thực sự chỉ cần gửi 1.

Bây giờ, giả sử bạn có một đối tượng JSON lưu trữ tất cả các vectơ phần tử của bạn. Đối tượng này được đồng bộ hóa giữa máy chủ của bạn và máy khách của bạn. Thay vì quyết định "đã làm như vậy và di chuyển?" bạn chỉ có thể tạo đánh dấu trò chơi tiếp theo của mình trong một đối tượng JSON, tạo diff tick100.json tick101.jsonvà gửi khác biệt đó. Về phía khách hàng, bạn áp dụng khác biệt cho vectơ hiện tại của bạn và bạn đã hoàn tất.

Làm điều này bạn tận dụng hàng thập kỷ chuyên môn trong việc phát hiện sự khác biệt trong văn bản (hoặc nhị phân!) Và không phải lo lắng về việc bỏ lỡ bất cứ điều gì cho mình. Bây giờ lý tưởng là bạn cũng sử dụng một thư viện thực hiện điều này đằng sau hậu trường để giúp bạn trở thành một nhà phát triển dễ dàng hơn nữa.


2
Sử dụng diffâm thanh như một hack không hiệu quả. Giữ chuỗi JSON ở đầu nhận, vá và giải tuần tự hóa nó mỗi lần là không cần thiết. Tính toán sự khác biệt của hai từ điển khóa-giá trị không phải là một nhiệm vụ phức tạp, về cơ bản, bạn chỉ cần lặp qua tất cả các khóa và kiểm tra xem các giá trị có bằng nhau không. Nếu không, bạn thêm chúng vào dict key-value và cuối cùng bạn gửi dict được tuần tự hóa tới JSON. Đơn giản, không cần nhiều năm chuyên môn. Không giống như diffs, phương pháp này: 1) sẽ không bao gồm dữ liệu cũ (được thay thế); 2) chơi độc đáo hơn với UDP; 3) không dựa vào dòng mới
gronostaj

8
@gronostaj Đây là một ví dụ để có được điểm. Tôi thực sự không ủng hộ việc sử dụng diff cho JSON - đó là lý do tại sao nói "giả sử".
corsiKa

2
"Làm điều này bạn tận dụng hàng thập kỷ chuyên môn trong việc phát hiện sự khác biệt trong văn bản (hoặc nhị phân!) Và không phải lo lắng về việc bỏ lỡ bất cứ điều gì. Bây giờ, lý tưởng nhất là bạn cũng sử dụng một thư viện thực hiện điều này phía sau hậu trường để bạn thực hiện dễ dàng hơn với bạn như một nhà phát triển. " Phần đó chắc chắn làm cho âm thanh giống như bạn đang đề xuất sử dụng diff hoặc sử dụng thư viện sử dụng diff khi không ai có thể làm điều đó một cách hợp lý. Tôi sẽ không gọi nén delta là "diffing", đó chỉ là nén delta, những điểm tương đồng là hời hợt
Selali Adobor

Một khác biệt tối ưu và nén delta tối ưu là như nhau. Mặc dù tiện ích diff trên dòng lệnh được định hướng cho các tệp văn bản và có thể sẽ không cung cấp cho bạn một kết quả tối ưu, tôi khuyên bạn nên nghiên cứu các thư viện thực hiện nén delta cho bạn. Không có gì lạ mắt về từ delta - delta và diff, theo nghĩa này, có nghĩa đen là cùng một thứ. Điều đó dường như đã bị mất trong những năm qua.
corsiKa

9

Rất thường một cơ chế nén khác sẽ kết hợp với mã hóa delta như nén số học.

Các sơ đồ nén này hoạt động tốt hơn nhiều khi các giá trị có thể được nhóm lại với nhau theo dự đoán. Mã hóa Delta sẽ nhóm các giá trị xung quanh 0.


1
Một ví dụ khác: nếu bạn có 100 tàu vũ trụ mỗi vị trí có vị trí riêng nhưng di chuyển ở cùng một vectơ vận tốc, bạn chỉ cần gửi vận tốc một lần (hoặc ít nhất là nó thực sự nén tốt); nếu không, bạn cần gửi 100 vị trí thay thế. Hãy để người khác làm thêm. Nếu bạn coi mô phỏng bước khóa trạng thái dùng chung là một hình thức nén delta mạnh mẽ, bạn thậm chí không gửi tốc độ - chỉ các lệnh đến từ trình phát. Một lần nữa, hãy để mọi người làm thêm.
Luaan

Tôi đồng ý. Nén có liên quan trong câu trả lời.
Leopoldo Sanchot

8

Bạn nói đúng, nhưng thiếu một điểm quan trọng.

Các thực thể trong một trò chơi được mô tả bởi nhiều thuộc tính, trong đó vị trí chỉ là một .

Những loại thuộc tính? Không cần phải suy nghĩ quá nhiều, trong một trò chơi nối mạng, chúng có thể bao gồm:

  • Chức vụ.
  • Sự định hướng.
  • Số khung hiện tại.
  • Thông tin màu sắc / ánh sáng.
  • Minh bạch.
  • Mô hình để sử dụng.
  • Kết cấu để sử dụng.
  • Hiệu ứng đặc biệt.
  • V.v.

Nếu bạn chọn từng cái một cách riêng biệt, bạn chắc chắn có thể đưa ra trường hợp rằng nếu trong bất kỳ khung cụ thể nào, nó cần thay đổi, thì nó phải được truyền lại đầy đủ.

Tuy nhiên, không phải tất cả các thuộc tính này đều thay đổi theo cùng một tỷ lệ .

Mô hình không thay đổi? Không có nén delta, dù sao nó cũng phải được truyền lại. Với đồng bằng delta thì không cần.

Vị trí và định hướng là hai trường hợp thú vị hơn, thường được tạo thành từ 3 phao mỗi cái. Giữa bất kỳ hai khung hình nào, có khả năng chỉ 1 hoặc 2 của mỗi bộ 3 phao có thể thay đổi. Di chuyển theo đường thẳng? Không quay? Không được nhảy qua? Đây là tất cả các trường hợp không có nén delta, bạn phải truyền lại đầy đủ, nhưng với nén delta bạn chỉ cần truyền lại mà thay đổi.


8

Bạn có quyền tự mình tính toán delta, với kết quả được lưu trữ trong cùng cấu trúc dữ liệu với các toán hạng và được truyền đi mà không cần xử lý thêm sẽ không lưu bất kỳ lưu lượng nào.

Tuy nhiên, có hai cách một hệ thống được thiết kế tốt dựa trên đồng bằng có thể tiết kiệm lưu lượng.

Thứ nhất, trong nhiều trường hợp, delta sẽ bằng không. Bạn có thể thiết kế giao thức của mình sao cho nếu đồng bằng bằng 0 thì bạn hoàn toàn không gửi nó. Rõ ràng, có một số chi phí cho việc này vì bạn phải cho biết những gì bạn đang hoặc không gửi, nhưng nhìn chung nó có thể là một chiến thắng lớn.

Thứ hai, đồng bằng thường sẽ có phạm vi giá trị nhỏ hơn nhiều so với số ban đầu. và phạm vi đó sẽ được tập trung vào số không. Điều này có thể được khai thác bằng cách sử dụng một loại dữ liệu nhỏ hơn cho hầu hết các vùng đồng bằng hoặc bằng cách chuyển luồng dữ liệu hoàn chỉnh thông qua thuật toán nén cho mục đích chung.


6

Mặc dù hầu hết các câu trả lời đều nói về cách mã hóa delta chỉ gửi toàn bộ các thay đổi về trạng thái, nhưng có một thứ khác gọi là "mã hóa delta" có thể được sử dụng làm bộ lọc để giảm lượng dữ liệu bạn cần nén ở trạng thái đầy đủ cập nhật là tốt, có thể là nơi mà sự nhầm lẫn đến từ câu hỏi như được hỏi.

Khi mã hóa một vectơ số, trong một số trường hợp, bạn có thể sử dụng mã số byte, enums, v.v.) sử dụng mã hóa byte biến cho các phần tử riêng lẻ và trong một số trường hợp này, bạn có thể giảm thêm lượng dữ liệu mà mỗi phần tử cần nếu bạn lưu trữ nó dưới dạng tổng tiền đang chạy hoặc là giá trị tối thiểu và chênh lệch giữa mỗi giá trị và mức tối thiểu đó.

Ví dụ, nếu bạn muốn mã hóa vectơ {68923, 69012, 69013, 69015}thì bạn có thể mã hóa delta đó thành {68923, 89, 1, 2}. Sử dụng mã hóa byte biến số tầm thường trong đó bạn lưu trữ 7 bit dữ liệu trên mỗi byte và sử dụng một bit để chỉ ra rằng có một byte khác sắp tới, mỗi phần tử riêng lẻ trong mảng sẽ yêu cầu 3 byte để truyền nó, nhưng phiên bản được mã hóa delta sẽ chỉ yêu cầu 3 byte cho phần tử đầu tiên và 1 byte cho các phần tử còn lại; tùy thuộc vào loại dữ liệu bạn sắp đặt tuần tự, điều này có thể dẫn đến một số khoản tiết kiệm khá ấn tượng.

Tuy nhiên, đây không chỉ là tối ưu hóa tuần tự hóa và không phải là điều thường có nghĩa khi chúng ta nói về "mã hóa delta" khi nói đến việc truyền dữ liệu tùy ý như một phần của trạng thái trò chơi (hoặc video hoặc tương tự); câu trả lời khác đã làm một công việc đầy đủ để giải thích điều đó.


4

Cũng đáng lưu ý rằng các thuật toán nén thực hiện công việc của chúng tốt hơn trên diff. Vì các câu trả lời khác đề cập đến hầu hết các yếu tố của bạn giữ nguyên giữa 2 trạng thái hoặc các giá trị thay đổi theo một phần nhỏ. Trong cả hai trường hợp, áp dụng thuật toán nén cho sự khác biệt của vectơ số của bạn mang lại cho bạn khoản tiết kiệm đáng kể. Ngay cả khi bạn không áp dụng bất kỳ logic bổ sung nào cho vectơ của mình như xóa các phần tử 0.

Đây là một ví dụ trong 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)))

Cung cấp cho tôi:

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

Ví dụ tốt đẹp. Nén đóng một vai trò ở đây.
Leopoldo Sanchot

0

Nếu vị trí của bạn được lưu trữ trong vector3 nhưng thực thể chỉ có thể di chuyển một vài số nguyên tại một thời điểm. Sau đó gửi delta của nó theo byte sẽ tốt hơn gửi nó theo số nguyên.

Vị trí hiện tại: 23009.0, 1.0, 2342.0 (3 float)
Vị trí mới: 23010.0, 1.0, 2341.0 (3 float)
Delta: 1, 0, -1 (3 byte)

Thay vì gửi vị trí chính xác mỗi lần, chúng tôi gửi delta.


0

Nén Delta là nén các giá trị được mã hóa delta. Mã hóa Delta là một phép biến đổi tạo ra phân phối thống kê các số khác nhau. Nếu phân phối thuận lợi cho thuật toán nén được chọn, nó sẽ làm giảm lượng dữ liệu. Nó hoạt động rất tốt trong một hệ thống giống như một trò chơi trong đó các thực thể chỉ di chuyển nhẹ giữa hai bản cập nhật.

Giả sử bạn có 100 thực thể trong 2D. Trên một lưới lớn, 512 x 512. Chỉ xem xét các số nguyên cho ví dụ. Đó là hai số nguyên cho mỗi thực thể hoặc 200 số.

Giữa hai bản cập nhật, tất cả các vị trí của chúng tôi thay đổi bằng 0, 1, -1, 2 hoặc -2. Đã có 100 trường hợp 0, 33 trường hợp 1 và -1 và chỉ có 17 trường hợp 2 và -2. Điều này là khá phổ biến. Chúng tôi chọn mã hóa Huffman để nén.

Cây Huffman cho điều này sẽ là:

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

Tất cả số 0 của bạn sẽ được mã hóa dưới dạng một bit. Đó chỉ là 100 bit. 66 giá trị sẽ được mã hóa thành 3 bit và chỉ 34 giá trị là 4 bit. Đó là 434 bit hoặc 55 byte. Cộng với một số chi phí nhỏ để lưu cây bản đồ của chúng tôi, vì cây rất nhỏ. Lưu ý rằng để mã hóa 5 số, bạn cần 3 bit. Chúng tôi đã giao dịch ở đây khả năng sử dụng 1 bit cho '0' cho nhu cầu sử dụng 4 bit cho '-2'.

Bây giờ so sánh điều này với việc gửi 200 số tùy ý. Nếu các thực thể của bạn không thể ở trên cùng một ô, bạn gần như được đảm bảo rằng bạn nhận được phân phối thống kê xấu. Trường hợp tốt nhất sẽ là 100 số duy nhất (tất cả trên cùng một X với Y khác nhau). Đó là ít nhất 7 bit cho mỗi số (175 byte) và rất khó cho bất kỳ thuật toán nén nào.

Nén delta hoạt động trong trường hợp đặc biệt khi các thực thể của bạn chỉ thay đổi một chút. Nếu bạn có nhiều thay đổi độc đáo, mã hóa delta sẽ không giúp ích gì.


Lưu ý rằng mã hóa và nén delta cũng được sử dụng trong các tình huống khác với các phép biến đổi khác.

MPEG chia hình ảnh thành các ô vuông nhỏ và nếu một phần của hình ảnh di chuyển, chỉ có chuyển động và thay đổi là độ sáng được lưu. Trong một bộ phim 25fps, rất nhiều thay đổi giữa các khung hình là rất nhỏ. Một lần nữa, mã hóa delta + nén. Hoạt động tốt nhất cho các cảnh tĩnh.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.