增量压缩如何减少通过网络发送的数据量?


26

许多游戏使用增量压缩技术来降低发送的数据负载。我不明白这种技术实际上如何降低数据负载?

例如,假设我要发送职位。如果不进行增量压缩,则发送vector3带有实体确切位置的,例如(30, 2, 19)。使用增量压缩时,我发送的vector3数字较小(0.2, 0.1, 0.04)

我不明白如果两条消息都是vector3-每个浮点数为32位-32 * 3 = 96位,它将如何降低数据负载!

我知道您可以将每个浮点数转换为一个字节,然后将其从字节转换回浮点数,但这会导致可见的精度错误。


每当您与使用任何形式的增量压缩的游戏进行网络连接时,都需要完全确定性(失败,并出现不同步)。“从字节转换为浮点数”(或任何其他形式)是否会导致精度错误并不重要-必要条件是使所有同步机/游戏中的所有内容完全相同。如果存在“精度错误”(即使使用了完整的浮点数,这也是不可避免的-您的CPU使用的浮点数与我的CPU不同),则它们在所有计算机上都必须相同,因此可见。您选择了类型以避免可见效果。
a安

2
@Luaan或您可以经常添加部分绝对状态,例如,您可以经常选择一些实体并传递绝对位置,更喜欢选择靠近玩家的实体。
棘轮怪胎

不知怎的,我期待这个问题是关于一些相对rsync的 ...
桑布

霍夫曼编码。
Ben Voigt

只使用中间人
约翰·德米特里

Answers:


41

有时候,您无法避免发送完整的游戏状态-例如在加载已保存的多人游戏时,或者需要重新同步时。但是发送完整状态通常是可以避免的,这就是增量编码的目的。通常,这就是增量压缩的全部内容。您的示例并没有真正描述这种情况。甚至提到增量压缩的原因是因为幼稚的实现通常会发送状态而不是增量,因为状态通常是任何幼稚游戏实现都会存储的内容。因此,Deltas是一种优化。

有了增量,您将永远不会发送不动的单位的位置。这就是它的精神。

想象我们是笔友多年,而我失去了记忆(在阅读完您的所有信件后就把它们丢弃了)。与其像平常一样简单地继续写一系列书信,还不如用一封大写信给我写一生的全部历史,以便我追上。

在给定的情况下,与发送完整状态所需的较大位范围相比,有可能(取决于您的代码库)使用较少数量的位对增量进行编码。假设世界跨过许多公里,您可能需要一个32位浮点数才能准确编码位置(例如,给定轴上的厘米)。但是,给定适用于实体的最大速度(每个滴答可能只有几米),它可能仅用8位(2 ^ 8 = 256,足以存储最大200cm)是可行的。当然,这是假设使用定点数而不是浮点数……或者如果您不希望使用定点数麻烦,则使用某种类似于OpenGL的半/四分之一浮点数。


7
你的答案对我来说还不清楚。我觉得不发送不动对象的信息只是增量编码的副作用,而不是“它的精神”。棘轮怪胎的答案似乎更好地强调了使用增量编码的真正原因。
Etsitpab Nioliv '17

14
@EtsitpabNioliv“精神”就是“不要发送不需要的信息”。可以将其降低到位级别-“仅使用所需的带宽,以通过有线方式获取所需的消息”。这个答案显然对其他所有人都足够清楚。谢谢。
工程师

4
@EtsitpabNioliv是否曾经了解SVN如何在服务器端存储文件?它不会存储每个提交的整个文件。它存储deltas,每个提交包含的更改。术语“ delta”通常在数学和编程中用来表示两个值之间的。我不是游戏程序员,但是如果游戏中的用法有很大不同,我会感到惊讶。这样的想法就很有意义:您只发送差异而不是发送所有差异,即可“压缩”必须发送的数据量。(如果它的任何部分让我感到困惑,那就是“压缩”一词。)
jpmc26

1
同样,数量少的零位数量更多,使用适当的压缩算法对发送的信息进行编码/解码可以导致通过网络发送的有效负载更小。
liggiorgio '17

22

您的三角洲错误。您正在查看各个元素的差异。您需要考虑整个场景的增量。

假设场景中有100个元素,但其中只有1个被移动。如果您发送100个元素向量,则会浪费其中的99个。您实际上只需要发送1。

现在,假设您有一个JSON对象,用于存储所有元素向量。此对象在服务器和客户端之间同步。而不是决定“某某动作了吗?” 您只需在JSON对象中生成您的下一个游戏滴答声,制作一个diff tick100.json tick101.json并发送差异即可。在客户端,将diff应用于当前刻度的矢量,一切就绪。

这样一来,您就可以利用数十年的专业知识来检测文本(或二进制!)之间的差异,而不必担心自己遗漏任何东西。现在,理想情况下,您还可以使用一个在后台执行此操作的库,以使您作为开发人员更加轻松。


2
使用diff听起来像是效率低下的骇客。不需要在接收端保留JSON字符串,无需每次对其进行修补和反序列化。计算两个键值字典的差值并不是一件复杂的事情,基本上,您只需遍历所有键并检查值是否相等。如果不是,则将它们添加到结果键值字典中,最后将序列化的字典发送到JSON。简单,不需要多年的专业知识。与diffs不同,此方法:1)不包含旧的(替换的)数据;2)与UDP配合使用更流畅;3)不依赖换行符
gronostaj

8
@gronostaj这是一个阐明观点的例子。我实际上不主张将diff用于JSON-这就是为什么说“假设”。
corsiKa's

2
“这样做,您可以利用数十年的专业知识来检测文本(或二进制!)之间的差异,而不必担心自己遗漏任何东西。现在,理想情况下,您还可以使用一个在后台执行此操作的库来使它变得均匀作为开发人员,您会更轻松。” 这部分肯定听起来像是您建议使用diff或使用没有差异的库来使用diff。我不会将增量压缩称为“差异”,这只是增量压缩,相似之处是肤浅的
Selali Adob​​or

最佳差异和最佳增量压缩是相同的。虽然命令行上的diff实用程序仅适用于文本文件,可能无法为您提供最佳效果,但我建议您研究可以为您做增量压缩的库。delta这个词没有什么特别的-从这个意义上讲,delta和diff的意思是相同的。这些年来似乎已经消失了。
corsiKa '17

9

通常,另一种压缩机制将与增量编码相结合,例如算术压缩。

当将可能的值可预测地分组在一起时,这些压缩方案会更好地工作。增量编码会将值分组为0左右。


1
再举一个例子:如果您有100个太空飞船,每个飞船都有自己的位置,但以相同的速度矢量行进,则只需要发送一次速度(或者至少将其压缩很好);否则,您需要发送100个职位。让其他人进行添加。如果您认为共享状态锁步模拟是增量压缩的一种激进形式,则您甚至不会发送速度-仅发送来自播放器的命令。同样,让每个人都自己添加。
a安

我同意。压缩与答案有关。
Leopoldo Sanczyk

8

您大体上是正确的,但缺少一个要点。

游戏中的实体由许多属性来描述,其中位置仅是一个

什么样的属性?在网络游戏中,不必太费力地思考,它们可以包括:

  • 位置。
  • 取向。
  • 当前帧号。
  • 颜色/照明信息。
  • 透明度。
  • 使用的模型。
  • 使用的纹理。
  • 特殊效果。
  • 等等。

如果您单独选择其中的每一个,那么您肯定可以确定,如果在任何给定的帧中需要更改它,那么必须将其完全重传。

但是,并非所有这些属性都以相同的速率改变

型号不变吗?如果没有增量压缩,则无论如何都必须重新传输它。有了delta压缩,就不必如此。

位置和方向是两种更有趣的情况,通常分别由3个浮点组成。在任何给定的两个帧之间,每3个浮点组中只有1个或2个可能发生变化。直线运动?不旋转?不跳?这些都是在没有增量压缩的情况下您必须完全重新传输的情况,而在具有增量压缩的情况下,您仅需要重新传输发生变化的内容。


8

没错,仅凭单纯的增量计算,将结果存储在与操作数相同的大小数据结构中,并且不进行任何进一步处理就可以传输数据,这不会节省任何流量。

但是,基于增量的精心设计的系统可以通过两种方式节省流量。

首先,在许多情况下,增量将为零。您可以设计协议,以使如果增量为零,则根本不会发送该协议。显然,这样做有一定的开销,因为您必须指出您正在发送或未发送的内容,但总的来说这可能是一个巨大的胜利。

其次,增量通常将具有比原始数字小得多的值范围。并且该范围将以零为中心。通过对大多数增量使用较小的数据类型,或通过将通用数据流传递给通用压缩算法,可以利用这一点。


6

尽管大多数答案都谈到增量编码是如何仅将状态更改整体发送出去的,但是还有另一种称为“增量编码”的东西可以用作过滤器,以减少在完整状态下需要压缩的数据量以及更新,这可能是所问问题中混乱的根源。

在对数字矢量进行编码时,可以在某些情况下(例如整数,枚举等)对各个元素使用可变字节编码,在某些情况下,您可以进一步减少每个元素所需的数据量如果将其存储为运行总和或最小值,以及每个值与最小值之间的差。

例如,如果您想对向量进行编码,{68923, 69012, 69013, 69015}则可以将其增量编码为{68923, 89, 1, 2}。使用平凡的可变字节编码,其中每个字节存储7位数据,并使用一位指示另一个字节要来,数组中的每个元素都需要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

很好的例子。压缩在这里起作用。
Leopoldo Sanczyk

0

如果您的位置存储在vector3中,但实际实体一次只能移动几个整数。然后,以字节为单位发送它的增量要比以整数发送它更好。

当前位置:23009.0,1.0,2342.0(3浮点数)
新位置:23010.0,1.0,2341.0(3浮点数)
增量:1,0,-1(3字节)

我们不是每次发送精确位置,而是发送增量。


0

增量压缩是增量编码值的压缩。增量编码是一种转换,会产生不同的数字统计分布。如果分布有利于选择的压缩算法,则它将减少数据量。它在像游戏这样的系统中效果很好,在该系统中,实体在两次更新之间仅略微移动。

假设您有100个2D实体。在大型网格上,为512 x512。为示例起见,仅考虑整数。这是每个实体两个整数或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将图片分割为小方块,如果图片的一部分移动,则仅保存移动和亮度变化。在25fps的电影中,帧之间的许多变化很小。同样,增量编码+压缩。最适合静态场景。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.