Xdelta3是一种优秀的、被广泛使用的差量更新算法,它在操作上既有对新文件(targetfile)和旧文件(sourcefile)的差分(differencing)又有对产生的patch包进行压缩(compression),我们将产生patch包的过程统称为加密(encoding),而将合成新文件的过程统称为解密(decoding)。Xdelta3和经典的压缩算法LZ’77一样,也是将source file划分成一个个不相交而又连续的window,然后进行encoding和decoding。
设target file的大小为n,source file的大小为m,window的大小为w。由于Xdelta3在合成target file所消耗的时间为O(n),所消耗的内存大小为O(w),所以该压缩算法很适合被移植到手机中。
Xdelta3在产生patch包的时候,主要依靠三个方面:
1.采用Vcdiff格式编码,节省patch包字节;
2.从target file和source file的内容上做差分;
3.压缩patch包。
Vcdiff格式编码
这是一种基于字节的编码,可移植性强。采用128进制,用一个字节的低七位表示数字,最高位标记编码是否完成,如果没有编完,编码将扩展到下一个字节。如:
123456789
表示为128进制为:
58,111,26,21
在机器中表示如下:
+-------------------------------------------------------+
| 10111010 | 11101111 |10011010 | 00010101 |
+-------------------------------------------------------+
MSB+58 MSB+111 MSB+26 0+21
这里,原本要用9个字节,变成了4个字节。
Target file和Sourcefile的差分
我们用S表示sourcefile的字符串,长度为S_len,T表示targetfile的字符串。差分用到add,run,copy三种命令。Xdelta3中,差分的copy命令不但可以在S与T之间执行,还可以在T已被解压的部分和未被解压的部分执行。
ADD x,s: 将一个长度为x的字符串s拷贝到T的当前位置。
COPY x,y: 当y<S_len时,从S[y]拷贝x个字符到T的当前位置;
当y≥S_len时,从T[y-S_len]拷贝x个字符到T的当前位置。
RUN x,z:在T的当前位置加x个字符z。
我们给出例子如下:
Source file a b c d e f g h i j kl m n o p
Target file a b c d w x y z e f g h e f g h e f g h e f g h z z z z
COPY 4, 0
ADD 4, w x y z
COPY 4, 4
COPY 12, 24
RUN 4, z
对于第四条命令,由于S的长度为16, 拷贝指针c指向T中的位置24-16=8,T当前位置的指针p指向位置12,执行T[p++]=T[c++]12次。
压缩Patch包
由上一部分可知,patch包中的内容可以由一系列add,run,copy命令表示。为了使patch包更小,这些命令必须被更有效的压缩。为了使压缩效率更高,Xdelta3将patch中的内容分了三部分,数据部分(DATA)、指令部分(INSTRUCTION)以及地址部分(ADDRESS),分别放在三个不同的数组,基于Vcdiff编码压缩:
DATA :ADD 和 RUN 命令中附带的数据,如字符串s和字符z(存放在数组data
中);
INSTRUCTION:一系列ADD、COPY、RUN命令本身和它们附带表示size的数字x(命令
存在数组inst中,x存在inst或code table中);
ADDRESS :COPY命令中附带的地址y(存放在数组addr中)。
DATA部分简单地按字节压缩、按顺序存放在数组data中即可,当执行的指令需要用到这个参数时,根据Vcdiff的编码原则,按顺序读取一个或多个字节,就可以获得该指令对应的data,如RUN中的字符和ADD中的字符串。
ADDRESS部分为COPY命令提供地址参数,和DATA部分的原理类似,但其在压缩和解压过程中,用到了cache以便加速,其中解压过程的cache会自动与压缩过程的cache同步。near 和same两种不同的cache对已两种不同的压缩方式。
INSTRUCTION部分的压缩较为复杂。由于剥离了存放在data和addr中的数据,现在我们可以用元组(inst, size, mode)来表示一条命令。Inst表示指令是ADD、RUN或COPY;size 就是命令中携带的x,其值为0的时候表示它被放在inst数组中,像上面提到的DATA、ADDRESS两个部分一样独立编码,不为0时被放在code table中可被直接读出;mode表示地址编码的压缩方式,当inst的值为COPY时,这个值才有效,其余的时候这个值都为0。
为了压缩b部分,需要在内存里保存一张code table,这个table的每个index对应了两个操作
+-----------------------------------------------------+
| inst1 | size1 | mode1 | inst2 | size2 | mode2 |
+----------------------------------------------------+
默认的code table可以满足现今绝大多数的情况下的需求,如下:
TYPE SIZE MODE TYPE SIZE MODE INDEX
-----------------------------------------------------------------------------------------------------
1. RUN 0 0 NOOP 0 0 0
2. ADD 0, [1,17] 0 NOOP 0 0 [1,18]
3. COPY 0, [4,18] 0 NOOP 0 0 [19,34]
4. COPY 0, [4,18] 1 NOOP 0 0 [35,50]
5. COPY 0, [4,18] 2 NOOP 0 0 [51,66]
6. COPY 0, [4,18] 3 NOOP 0 0 [67,82]
7. COPY 0, [4,18] 4 NOOP 0 0 [83,98]
8. COPY 0, [4,18] 5 NOOP 0 0 [99,114]
9. COPY 0, [4,18] 6 NOOP 0 0 [115,130]
10. COPY 0, [4,18] 7 NOOP 0 0 [131,146]
11. COPY 0, [4,18] 8 NOOP 0 0 [147,162]
12. ADD [1,4] 0 COPY [4,6] 0 [163,174]
13. ADD [1,4] 0 COPY [4,6] 1 [175,186]
14. ADD [1,4] 0 COPY [4,6] 2 [187,198]
15. ADD [1,4] 0 COPY [4,6] 3 [199,210]
16. ADD [1,4] 0 COPY [4,6] 4 [211,222]
17. ADD [1,4] 0 COPY [4,6] 5 [223,234]
18. ADD [1,4] 0 COPY 4 6 [235,238]
19. ADD [1,4] 0 COPY 4 7 [239,242]
20. ADD [1,4] 0 COPY 4 8 [243,246]
21. COPY 4 [0,8] ADD 1 0 [247,255]
------------------------------------------------------------------------------------------------------
其中NOOP表示无任何操作。Index的值覆盖0~255,可以用一个字节表示。有了code table,我们编码过程中,可以将指令写成(index, [size1],[size2])形式放在数组inst中。
比如我们给出index=19,那么我们在code table中查找,第3行的INDEX中包含了19,这一行对应了两个连续的命令,COPY和NOOP。COPY的对应的SIZE部分包含了0,4~18这16个值,其中0与19对应,即这条命令的size被独立压缩编码,我们需要从inst数组中读出size1。如果是index=165,它对应第12行。第12行有两个操作,两个SIZE应该以第一个SIZE的取值范围做外层循环,第二个SIZE的范围做内层循环,组合展开如下:
TYPE SIZE MODE TYPE SIZE MODE INDEX
------------------------------------------------------------------------------------------------------
ADD 1 0 COPY 4 0 163
ADD 1 0 COPY 5 0 164
ADD 1 0 COPY 6 0 165
ADD 2 0 COPY 4 0 166
ADD 2 0 COPY 5 0 167
ADD 2 0 COPY 6 0 168
ADD 3 0 COPY 4 0 169
TYPE SIZE MODE TYPE SIZE MODE INDEX
-----------------------------------------------------------------------------------------------------
ADD 3 0 COPY 5 0 170
ADD 3 0 COPY 6 0 171
ADD 4 0 COPY 4 0 172
ADD 4 0 COPY 5 0 173
ADD 4 0 COPY 6 0 174
------------------------------------------------------------------------------------------------------
我们可以将165解密成两个连续的操作:1.在T[p]处添加一个字符,该字符以Vcdiff编码的方式存放在data[data_p],p++,data_p++;2.拷贝6个字节到T[p...p+5]处,p=p+6,被拷贝文件的地址以Vcdiff编码+mode 0的方式编码存放在addr[addr_p]。
总结
Xdelta3使用基于字节的Vcdiff编码,可以在一种机器上encoding而在另一种机器上decoding,且encoding与decoding的过程相对独立,可移植性强。Patch包被分为三个独立的部分,便于使用不同的技术来分别改进它们的编码、压缩,并且支持二次压缩。Decoding的时间与内存消耗为线性。而encoding的过程中,最主要的部分为每次寻找最大匹配串的string matching过程,这个过程可采用hash,suffix trees,fast string matching等技术来优化,但其时间内存消耗对大文件而言仍不可接受,使用windows划分这一技术有效的解决了问题。window的大小是一个重要的参数,window越大,最长字符串匹配的结果越精确,产生的patch可能越小,而时间内存消耗则变大;window越小,则反之。
更多内容,参考译文:
The VCDIFF Generic Differencing and Compression Data Format http://tools.ietf.org/html/rfc3284