大部分静态数据流的 CRC 计算
Posted
技术标签:
【中文标题】大部分静态数据流的 CRC 计算【英文标题】:CRC Calculation Of A Mostly Static Data Stream 【发布时间】:2014-06-01 02:03:19 【问题描述】:背景:
我有一段内存,1024 字节。最后 1020 个字节将始终相同。前 4 个字节会改变(产品的序列号)。我需要计算整个内存部分的CRC-16
CCITT
(0xFFFF 起始,0x1021 掩码)CRC_WHOLE
。
问题:
是否可以只计算前 4 个字节的 CRC,CRC_A
,然后应用如下函数来计算完整的 CRC?我们可以假设最后 1020 个字节的校验和 CRC_B
是已知的。
CRC_WHOLE = XOR(CRC_A, CRC_B)
我知道这个公式不起作用(尝试过),但我希望存在类似的东西。
【问题讨论】:
有一个技术方案,如下所述。但是对于所需的努力以及由此产生的好处,是否值得?与仅执行 1024 字节的直接 CRC 相比,您希望获得什么好处? 【参考方案1】:是的。您可以在zlib 的crc32_combine()
中看到如何。如果你有两个序列A和B,那么AB的纯CRC是A0的CRC和0B的CRC的异或,其中0代表一系列具有相应序列长度的零字节,即B和 A。
对于您的应用程序,您可以预先计算一个运算符,该运算符非常快速地将 1020 个零应用于前四个字节的 CRC。然后你可以用预先计算的 1020 字节的 CRC 进行异或。
更新:
这是我 2008 年的一篇文章,其中包含@ArtemB 发现的详细解释(我已经忘记了):
zlib 中的crc32_combine()
基于两个关键技巧。对于接下来的内容,
我们搁置了标准 32 位 CRC 是前和后的事实
有条件的。我们可以稍后处理。现在假设一个 CRC
没有这样的条件,所以从填充的寄存器开始
零。
技巧 #1:CRC 是线性的。因此,如果您有流 X 和流 Y 相同的长度和异或两个流逐位得到Z, 即 Z = X ^ Y(使用 C 表示法进行异或),然后 CRC(Z) = CRC(X) ^ CRC(Y)。对于手头的问题,我们有两个流 A 和 B 我们想要连接到流 Z 中的不同长度的。什么 我们有 CRC(A) 和 CRC(B)。我们想要的是一个快速的方法 计算 CRC(Z)。诀窍是构造 X = A 连接 长度(B)零位,并且 Y = 长度(A)零位与 B 连接。 因此,如果我们仅通过并列 符号,X = A0,Y = 0B,然后 X^Y = Z = AB。然后我们有 CRC(Z) = CRC(A0) ^ CRC(0B)。
现在我们需要知道 CRC(A0) 和 CRC(0B)。 CRC(0B) 很简单。如果我们喂 CRC机器的一堆零,从零开始,寄存器 仍然用零填充。所以就好像我们什么都没做一样。 因此 CRC(0B) = CRC(B)。
CRC(A0) 然而需要更多的工作。采用非零 CRC 并喂食 CRC机器的零不会让它不管。每零变化 寄存器内容。所以要得到CRC(A0),我们需要设置寄存器 到CRC(A),然后运行长度(B)通过它为零。然后我们可以 异或 CRC(B) = CRC(0B) 的结果,我们得到什么 我们想要,即 CRC(Z) = CRC(AB)。瞧!
嗯,事实上,瞧,还为时过早。我一点都不满意 那个答案。我不想要一个需要时间的计算 与 B 的长度成正比。与此相比,这不会节省任何时间 只需将寄存器设置为 CRC(A) 并运行 B 流 通过。我认为必须有一种更快的方法来计算效果 将 n 个零输入 CRC 机器(其中 n = length(B))。所以 这导致我们:
技巧 #2:CRC 机器是线性状态机。如果我们知道 当我们向机器输入零时发生的线性变换, 然后我们可以更有效地对该转换进行操作 找到将 n 个零输入到 机器。
将单个零位输入 CRC 机器的转换 完全由一个 32x32 的二进制矩阵表示。要应用 变换我们将矩阵乘以寄存器,取 注册为 32 位列向量。对于矩阵乘法 二进制(即超过 2 的伽罗瓦域),乘法的作用 由and'ing扮演,加法的角色由exclusive-扮演 或'ing。
有几种不同的方法可以构建魔法矩阵 表示给 CRC 机器输入 a 引起的变换 单个零位。一种方法是观察矩阵的每一列 是当您的注册以一个单一的开始时您得到的 它。所以第一列是当寄存器为 100 时你得到的... 然后输入一个零,第二列来自于 0100...等(这些被称为基向量。)你可以看到 这只需对这些向量进行矩阵乘法即可。 矩阵乘法选择矩阵的列 对应单的位置。
现在是诀窍。一旦我们有了魔法矩阵,我们就可以搁置一旁 一段时间的初始寄存器内容,而不是使用 一个零的转换以计算 n 的转换 零。我们可以将矩阵的 n 个副本相乘得到 n 个零的矩阵。但这比仅仅运行 n 还要糟糕 通过机器归零。但是有一个简单的方法可以避免大多数 这些矩阵乘法得到相同的答案。假设我们 想知道运行八个零位或一位的转换 字节通过。我们称其为代表运行的魔法矩阵 零到:M。我们可以做七次矩阵乘法得到 R = MxMxMxMxMxMxMxM。相反,让我们从 MxM 开始并称其为 P。然后 PxP 是 MxMxMxM。我们称它为 Q。那么 QxQ 就是 R。所以现在我们有了 将七个乘法减少到三个。 P = MxM,Q = PxP,R = QxQ。
现在我确定您已经了解了任意 n 个零的想法。我们 可以非常快速地生成变换矩阵 Mk,其中 Mk 是 运行 2k 个零的转换。 (在里面 上面的段落 M3 是 R。)我们可以只用 k 使 M1 到 Mk 矩阵乘法,从 M0 = M 开始。k 只需为 与 n 的二进制表示中的位数一样大。我们可以 然后选择那些在二进制中有矩阵的矩阵 n 的表示并将它们相乘得到 通过 CRC 机器运行 n 个零的转换。所以如果 n = 13、计算M0 x M2 x M3.
如果j是n的二进制表示中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 的 crc32_combine()
所做的。
我将把它作为练习留给读者如何处理
CRC 寄存器的预处理和后处理。你只需要
应用上面的线性观察。提示:你不需要知道
长度(A)。事实上crc32_combine()
只需要三个参数:
CRC(A)、CRC(B) 和长度(B)(以字节为单位)。
【讨论】:
真棒直截了当的答案。感谢您的帮助! 可以将整数 (2) 的幂次方(模 CRC 多项式),而不是将矩阵的幂次幂,然后将 CRC 乘以 (2^n)%poly 来循环它n 位。我在答案中发布了示例代码。 @rcgldr zlib currently does it. @MarkAdler - 对于早期的 DAT/DDS 磁带格式,类似的问题在 1990 年左右首次出现,其中 C2 Reed Solomon 奇偶校验字节存储在代码字的中间。一个早期的计划是编码器通过无进位乘以 2^(255-n) 将正常计算的奇偶校验字节向后循环 n 位,但是由于 ECC 处理擦除和错误,“编码”通过将奇偶校验字节标记为来实现擦除并进行校正,驱动器最终没有真正的编码器。 @Arash 评论不是提问的地方。你需要问一个新问题。您还需要提供有关您想要做什么的更多信息。根据我对这句话的理解,CRC 总是“即时”计算的。【参考方案2】:以下是用于 CRC(A0) 的替代方法的示例 C 代码。不是使用矩阵,而是可以通过乘法 (CRC · ((2^n)%POLY)%POLY 将 CRC 向前循环 n 位。因此,重复平方是对整数而不是矩阵执行的。如果 n 是常数,则可以预先计算 (2^n)%POLY。
/* crcpad.c - crc - data has a large number of trailing zeroes */
#include <stdio.h>
#include <stdlib.h>
typedef unsigned char uint8_t;
typedef unsigned int uint32_t;
#define POLY (0x04c11db7u)
static uint32_t crctbl[256];
void GenTbl(void) /* generate crc table */
uint32_t crc;
uint32_t c;
uint32_t i;
for(c = 0; c < 0x100; c++)
crc = c<<24;
for(i = 0; i < 8; i++)
/* assumes twos complement */
crc = (crc<<1)^((0-(crc>>31))&POLY);
crctbl[c] = crc;
uint32_t GenCrc(uint8_t * bfr, size_t size) /* generate crc */
uint32_t crc = 0u;
while(size--)
crc = (crc<<8)^crctbl[(crc>>24)^*bfr++];
return(crc);
/* carryless multiply modulo crc */
uint32_t MpyModCrc(uint32_t a, uint32_t b) /* (a*b)%crc */
uint32_t pd = 0;
uint32_t i;
for(i = 0; i < 32; i++)
/* assumes twos complement */
pd = (pd<<1)^((0-(pd>>31))&POLY);
pd ^= (0-(b>>31))&a;
b <<= 1;
return pd;
/* exponentiate by repeated squaring modulo crc */
uint32_t PowModCrc(uint32_t p) /* pow(2,p)%crc */
uint32_t prd = 0x1u; /* current product */
uint32_t sqr = 0x2u; /* current square */
while(p)
if(p&1)
prd = MpyModCrc(prd, sqr);
sqr = MpyModCrc(sqr, sqr);
p >>= 1;
return prd;
/* # data bytes */
#define DAT ( 32)
/* # zero bytes */
#define PAD (992)
/* DATA+PAD */
#define CNT (1024)
int main()
uint32_t pmc;
uint32_t crc;
uint32_t crf;
uint32_t i;
uint8_t *msg = malloc(CNT);
for(i = 0; i < DAT; i++) /* generate msg */
msg[i] = (uint8_t)rand();
for( ; i < CNT; i++)
msg[i] = 0;
GenTbl(); /* generate crc table */
crc = GenCrc(msg, CNT); /* generate crc normally */
crf = GenCrc(msg, DAT); /* generate crc for data */
pmc = PowModCrc(PAD*8); /* pmc = pow(2,PAD*8)%crc */
crf = MpyModCrc(crf, pmc); /* crf = (crf*pmc)%crc */
printf("%08x %08x\n", crc, crf);
free(msg);
return 0;
使用内部无进位乘法的示例 C 代码 pclmulqdq == _mm_clmulepi64_si128:
/* crcpadm.c - crc - data has a large number of trailing zeroes */
/* pclmulqdq intrinsic version */
#include <stdio.h>
#include <stdlib.h>
#include <intrin.h>
typedef unsigned char uint8_t;
typedef unsigned int uint32_t;
typedef unsigned long long uint64_t;
#define POLY (0x104c11db7ull)
#define POLYM ( 0x04c11db7u)
static uint32_t crctbl[256];
static __m128i poly; /* poly */
static __m128i invpoly; /* 2^64 / POLY */
void GenMPoly(void) /* generate __m12i8 poly info */
uint64_t N = 0x100000000ull;
uint64_t Q = 0;
for(size_t i = 0; i < 33; i++)
Q <<= 1;
if(N&0x100000000ull)
Q |= 1;
N ^= POLY;
N <<= 1;
poly.m128i_u64[0] = POLY;
invpoly.m128i_u64[0] = Q;
void GenTbl(void) /* generate crc table */
uint32_t crc;
uint32_t c;
uint32_t i;
for(c = 0; c < 0x100; c++)
crc = c<<24;
for(i = 0; i < 8; i++)
/* assumes twos complement */
crc = (crc<<1)^((0-(crc>>31))&POLYM);
crctbl[c] = crc;
uint32_t GenCrc(uint8_t * bfr, size_t size) /* generate crc */
uint32_t crc = 0u;
while(size--)
crc = (crc<<8)^crctbl[(crc>>24)^*bfr++];
return(crc);
/* carryless multiply modulo crc */
uint32_t MpyModCrc(uint32_t a, uint32_t b) /* (a*b)%crc */
__m128i ma, mb, mp, mt;
ma.m128i_u64[0] = a;
mb.m128i_u64[0] = b;
mp = _mm_clmulepi64_si128(ma, mb, 0x00); /* p[0] = a*b */
mt = _mm_clmulepi64_si128(mp, invpoly, 0x00); /* t[1] = (p[0]*((2^64)/POLY))>>64 */
mt = _mm_clmulepi64_si128(mt, poly, 0x01); /* t[0] = t[1]*POLY */
return mp.m128i_u32[0] ^ mt.m128i_u32[0]; /* ret = p[0] ^ t[0] */
/* exponentiate by repeated squaring modulo crc */
uint32_t PowModCrc(uint32_t p) /* pow(2,p)%crc */
uint32_t prd = 0x1u; /* current product */
uint32_t sqr = 0x2u; /* current square */
while(p)
if(p&1)
prd = MpyModCrc(prd, sqr);
sqr = MpyModCrc(sqr, sqr);
p >>= 1;
return prd;
/* # data bytes */
#define DAT ( 32)
/* # zero bytes */
#define PAD (992)
/* DATA+PAD */
#define CNT (1024)
int main()
uint32_t pmc;
uint32_t crc;
uint32_t crf;
uint32_t i;
uint8_t *msg = malloc(CNT);
GenMPoly(); /* generate __m128 polys */
GenTbl(); /* generate crc table */
for(i = 0; i < DAT; i++) /* generate msg */
msg[i] = (uint8_t)rand();
for( ; i < CNT; i++)
msg[i] = 0;
crc = GenCrc(msg, CNT); /* generate crc normally */
crf = GenCrc(msg, DAT); /* generate crc for data */
pmc = PowModCrc(PAD*8); /* pmc = pow(2,PAD*8)%crc */
crf = MpyModCrc(crf, pmc); /* crf = (crf*pmc)%crc */
printf("%08x %08x\n", crc, crf);
free(msg);
return 0;
【讨论】:
可以不加0就计算出每个段的CRC吗?考虑我们有 2 个文件段(文件有 CRC) @Arash - 示例代码在计算 CRC 时就像添加了 0,而实际上并未将任何 0 附加到数据中。以上是关于大部分静态数据流的 CRC 计算的主要内容,如果未能解决你的问题,请参考以下文章