具有 PCLMULQDQ 的快速 CRC *未*反映

Fast CRC with PCLMULQDQ *NOT* reflected

我正在尝试编写一个 PCLMULQDQ-optimized CRC-32 implementation. The specific CRC-32 variant is for one that I don't own, but am trying to support in library form. In crcany model 表单,它具有以下参数:

width=32 poly=0xaf init=0xffffffff refin=false refout=false xorout=0x00000000 check=0xa5fd3138 (我认为遗漏的残留物是 0x00000000 但老实说不知道它是什么)

算法的基本 non-table-based/bitwise 实现(由 crcany 生成)是:

uint32_t crc32byond_bit(uint32_t crc, void const *mem, size_t len) {
    unsigned char const *data = mem;
    if (data == NULL)
        return 0xffffffff;
    for (size_t i = 0; i < len; i++) {
        crc ^= (uint32_t)data[i] << 24;
        for (unsigned k = 0; k < 8; k++) {
            crc = crc & 0x80000000 ? (crc << 1) ^ 0xaf : crc << 1;
        }
    }
    return crc;
}

首先,我从根本上不理解描述该算法的论文。我不知道 K1 = [x^(512+64) mod P(x)] 这样的东西是什么意思。 (什么是x?从哪里来的?我不知道。)我是一名专业的软件工程师;不是学者。坦率地说,我根本不理解这个符号,Wikipedia article 对我没有多大帮助。也许是我在线性代数方面的弱点又来困扰我了。

我知道一些 public 实现我希望吸取:

但是:

我在 soft-crc but it doesn't appear to use the same constants (K4-K6?μ?)

中找到了一个英特尔实现
/**
 * PCLMULQDQ CRC computation context structure
 */
struct crc_pclmulqdq_ctx {
        /**
         * K1 = reminder X^128 / P(X) : 0-63
         * K2 = reminder X^192 / P(X) : 64-127
         */
        uint64_t k1;
        uint64_t k2;

        /**
         * K3 = reminder X^64 / P(X) : 0-63
         * q  = quotient X^64 / P(X) : 64-127
         */
        uint64_t k3;
        uint64_t q;

        /**
         * p   = polynomial / P(X) : 0-63
         * res = reserved : 64-127
         */
        uint64_t p;
        uint64_t res;
};

相信 我需要的 poly 0xAF 常数是:

Px  = 0x1_0000_00AF
k1  = 0x0_5B5A_E0C7
k2  = 0x0_1BD8_1099
k3  = 0x0_1157_936A
k4  = 0x0_1010_1111
k5  = 0x0_0029_5F23
k6  = 0x0_0000_4455
μ   = 0x1_0000_00AF

(来源:print-crc32-x86-sse42-magic-numbers.goconst px = "100000000000000000000000010101111",或 0xaf | (1 << 32)


我希望在这两个方面得到帮助

  1. 理解文章中使用的符号和公式(以及为什么实现似乎与文章不同?),
  2. 为非反射变体转换现有实现,或者可能
  3. 一些伪代码?

我有16、32、64位crc的6组代码,non-reflected反映在这里。该代码是为 Visual Studio 设置的。注释已添加到英特尔 github 站点中缺失的常量。

https://github.com/jeffareid/crc

32 位 non-relfected 在这里:

https://github.com/jeffareid/crc/tree/master/crc32f

您需要更改 crc32fg.cpp 中的多项式,它会生成常量。你要的多项式其实是:

0x00000001000000afull

我对 crc32fg.cpp 进行了此更改,以在该答案的末尾生成 rk.. 常量。

what is x?

CRC 使用具有 1 位系数的多项式。例如 0x0B 实际上是 x^3 + x + 1.

XMM寄存器可以读|写16字节|一次 128 位,但 PCLMULQDQ 只能将两个 64 位操作数进行无进位乘法以产生 128 位乘积。所以 128 位被拆分成两个 64 位操作数,然后每个操作数乘以一个常数以“折叠”它向前。由于 XMM 寄存器可以并行操作,因此使用 8 个 XMM 寄存器来读取 |写入 128 个字节 |一次 1024 位。每个折叠步骤“前进” 16 个字节 | 128 位数据转发 128 个字节 | 1024 位,乘以常数。较低的 64 位乘以 x^(1024) mod poly 以创建一个“高级”1024 位的 128 位乘积。高64位乘以x^(1024+64) mod poly得到一个128位乘积,即高级字节1024+64位(需要+64,因为乘积是基于高64位的128 位数据)。两个 128 位产品被异或在一起,然后与数据 128 字节进行异或运算 |缓冲区中的 1024 位之后。

注意Intel文档中的例子使用了4个XMM寄存器来推进数据64字节|一次 512 位,但我看到的 github 示例和我在 github 存储库中使用的示例使用 8 个 XMM 寄存器并前进 128 个字节 |一次 1024 位。对于数量相对较少的支持 AVX512 的处理器 | ZMM寄存器,有前进256字节的例子 |一次 2048 位。我没有带 AVX512 的电脑,所以我没有任何代码。

由于 XMM 读|写是小端,PSHUFB 用于在每次读取后反转字节。

该代码主要基于使用 65 位多项式的 64 位 CRC,但对于 32 位 CRC,这可以通过将低 32 位设置为零来处理。对于 32 位 CRC,大多数常量只有 32 位,并向左移动 32 位以简化 PCLMULQDQ 的使用,并进行调整以补偿移位,因此不是 x^(a) mod poly,它是(x^(a-32) mod 聚)<<32。实际 33 位多项式及其“逆”x^64 / 多项式的常数是 33 位并且没有左移,而使用这些常数创建实际 CRC 的 64 位值被左移 32 位。

折叠过程不产生 CRC,它只是转换数据以推进它。处理完所有数据后,使用常量 rk09、rk10、...、rk19、rk20、rk01、rk02,将 8 个 XMM 寄存器折叠成一个 128 位值。此时(标签 _128_done:) 有 128 位数据,并且由于代码基于 64 位 CRC 逻辑,逻辑上附加了 64 位零,因此实际上它是一个 192 位值,其中虚数低 64 位全部为零。高 64 位向前折叠 128 位,导致 XMM7 中的 128 位值准备计算 64 位 CRC,但由于这是 32 位 CRC,XMM7 将 96 位数据左移 32 位。高 32 位向前折叠 64 位(在这种情况下,96 位值和 rk06 都左移 32 位,因此 rk06 在这种情况下折叠 64 位(x^64 mod poly)并移位左移 32 位。结果是一个 64 位值在 XMM7 中左移 32 位。

64位数除以33位多项式的商只需要64位数的高32位,所以左移64位的值有高32位,方便计算a商。除法实际上是乘以 x^64 / 多项式,PCLMULQDQ 将仅指定使用 XMM7 的高 64 位部分来使用左移 64 位数字的高 32 位。实际的CRC计算是基于这样的逻辑:

quotient = upper 32 bits of 64 bit value / polynomial
product  = quotient * polynomial
CRC      = 64 bit value XOR product

除法是通过乘以倒数来完成的:x^64 / poly。由于 poly 和它的逆是 33 位,它们不能左移 32 位,所以代码在每次乘法后将乘积左移 4 个字节。 CRC 以 XMM7 的第 32 位到第 63 位结束,pextrd eax,xmm7,1 用于获取这 32 位。


我 mod 使 crc32fg.cpp 使用 CRC 多项式 0x1000000af,这就是我得到的结果。对于这个多项式,rk07 == rk08,但对于其他多项式,它们将不同。

rk01    dq      000295f2300000000h      ; x^(32* 3) mod P(x) << 32
rk02    dq      0fafa517900000000h      ; x^(32* 5) mod P(x) << 32
rk03    dq      05cd86bb500000000h      ; x^(32*31) mod P(x) << 32
rk04    dq      0af6f37a300000000h      ; x^(32*33) mod P(x) << 32
rk05    dq      000295f2300000000h      ; x^(32* 3) mod P(x) << 32
rk06    dq      00000445500000000h      ; x^(32* 2) mod P(x) << 32
rk07    dq      000000001000000afh      ; floor(2^64/P(x))
rk08    dq      000000001000000afh      ; P(x)
rk09    dq      09bd57b5d00000000h      ; x^(32*27) mod P(x) << 32
rk10    dq      0b7a4d76400000000h      ; x^(32*29) mod P(x) << 32
rk11    dq      01ae0004200000000h      ; x^(32*23) mod P(x) << 32
rk12    dq      0e7720be600000000h      ; x^(32*25) mod P(x) << 32
rk13    dq      09c7fc8fe00000000h      ; x^(32*19) mod P(x) << 32
rk14    dq      03885faf800000000h      ; x^(32*21) mod P(x) << 32
rk15    dq      0b477ad7100000000h      ; x^(32*15) mod P(x) << 32
rk16    dq      00ac2ae3d00000000h      ; x^(32*17) mod P(x) << 32
rk17    dq      05eae9dbe00000000h      ; x^(32*11) mod P(x) << 32
rk18    dq      0784a483800000000h      ; x^(32*13) mod P(x) << 32
rk19    dq      07d21bf2000000000h      ; x^(32* 7) mod P(x) << 32
rk20    dq      0faebd3d300000000h      ; x^(32* 9) mod P(x) << 32