是的。您可以在
zlib的
crc32_combine()
中看到如何操作。如果有两个序列A和B,则AB的纯CRC是A0的CRC和0B的CRC的异或,其中0表示具有相应序列长度的零字节序列,即分别为B和A。
对于您的应用程序,您可以预先计算一个单一运算符,该运算符可以快速地将1020个零应用于您前四个字节的CRC。然后,您可以将其与预计算的1020字节的CRC进行异或。
更新:
这是我2008年发表的一篇帖子,@ArtemB发现了其中详细的解释(我已经忘记了):
The
crc32_combine()
function in zlib utilizes two key tricks for computation. For now, we will disregard the fact that the standard 32-bit CRC is pre and post-conditioned. Let us assume that the CRC has no such conditioning and starts with a register filled with zeros.
Trick #1: CRCs are linear. If we have two streams of identical length, X and Y, and we perform an exclusive-or operation between the two bit-by-bit to get Z (i.e., Z = X ^ Y), then CRC(Z) = CRC(X) ^ CRC(Y). In this problem, we have two streams, A and B, which have different lengths, and we want to concatenate them into stream Z. We have access to CRC(A) and CRC(B) but need a quick way to compute CRC(Z). The trick is to construct X = A concatenated with length(B) zero bits, and Y = length(A) zero bits concatenated with B. Using simple juxtaposition to represent concatenation, X = A0 and Y = 0B. Thus, X^Y = Z = AB, and we can calculate CRC(Z) as CRC(A0) ^ CRC(0B).
现在我们需要知道CRC(A0)和CRC(0B)。计算CRC(0B)很容易。如果我们从零开始向CRC机器输入一堆零,那么寄存器仍然填满了零。所以就好像我们什么也没做。因此CRC(0B) = CRC(B)。
然而,计算CRC(A0)需要更多的工作。将零输入到非零CRC机器中并不会使其保持不变。每个零都会改变寄存器内容。因此,要获取CRC(A0),我们需要将寄存器设置为CRC(A),然后运行长度为B的零。然后我们可以将其结果与CRC(B) = CRC(0B)异或,得到我们想要的CRC(Z) = CRC(AB)。哇!
实际上,Voila还为时过早。我对那个答案一点也不满意。我不想要一个与B的长度成比例的计算。这与简单地将寄存器设置为CRC(A)并通过B流运行不节省时间。我想知道有更快的方法来计算将n个零输入到CRC机器中的效果(其中n = B的长度)。所以这就引导我们到:
技巧2:CRC机器是一个线性状态机。如果我们知道当我们向机器中输入零时发生的线性变换,那么我们可以对该变换进行操作,以更有效地找到从机器中输入n个零后产生的变换。
将单个零位馈入CRC机器的变换完全由32x32二进制矩阵表示。要应用变换,我们将矩阵乘以寄存器,将寄存器作为32位列向量。对于二进制矩阵乘法(即在Galois Field of 2上),乘法的作用是and'ing,加法的作用是exclusive-or'ing。
有几种不同的方法可以构建表示输入单个零位给CRC机器所引起的转换的魔方阵。其中一种方法是观察魔方阵的每列,当您的寄存器以一个1位开始时,即可得到该列。因此,第一列是在寄存器为100...时并输入一个0所得到的,第二列来自于以0100...开头,等等(这些被称为基向量)。通过使用这些向量进行矩阵乘法便可轻松地看出这一点。矩阵乘法选择与单个1位置相对应的魔方阵列。
现在来介绍这个技巧。一旦我们有了魔术矩阵,我们就可以暂时搁置初始寄存器内容,而是使用一个零的变换计算n个零的变换。我们可以只是将矩阵的n份副本相乘以获得n个零的矩阵。但是,这甚至比直接运行n个零更糟糕。然而,有一种简单的方法可以避免大部分矩阵乘法,并获得相同的答案。假设我们想知道运行8个零位或一个字节所需的转换。让我们称代表运行一个零的神奇矩阵为M。我们可以进行七次矩阵乘法以获得R = MxMxMxMxMxMxMxM。相反,让我们从MxM开始,并将其称为P。然后PxP为MxMxMxM。让我们将其称为Q。然后QxQ为R。因此,现在我们将七次乘法减少到三次。P = MxM,Q = PxP,R = QxQ。
现在我确定你已经了解了任意数量的零的概念。 我们可以非常快速地生成变换矩阵M
k,其中M
k是运行2
k个零的转换。(在上面的段落中,M
3是R。)我们可以使用仅
k个矩阵乘法从M
0= M开始生成M
1到M
k。
k只需要与
n的二进制表示中的位数一样大即可。然后,我们可以选择那些在
n的二进制表示中为1的矩阵,并将它们相乘以获得通过CRC机器运行
n个零的变换。因此,如果
n = 13,则计算M
0 x M
2 x M
3。
如果
n的二进制表示中有
j个1,那么我们只需要再进行
j-1次矩阵乘法即可。因此,我们总共需要
k次乘法。
- j-1次矩阵乘法,其中j<=k=floor(logbase2(n))。
现在,我们将快速构建的
n个零的矩阵与CRC(A)相乘,以获得CRC(A0)。我们可以在O(log(n))时间内计算出CRC(A0),而不是O(n)时间。然后,我们将其与CRC(B)异或,就得到了CRC(Z)。
这就是zlib的函数所做的事情。
至于如何处理CRC寄存器的预处理和后处理,我将把它作为读者的练习。你只需要应用上面的线性观察结果。提示:你不需要知道length(A)。实际上,
crc32_combine()
函数只需要三个参数:CRC(A)、CRC(B)和length(B)(以字节为单位)。