如何在zlib CRC32中正确使用无进位乘法组件(PCLMULQDQ)?

时间:2016-05-22 11:30:13

标签: c assembly mathematical-optimization zlib crc32

我最近一直在玩CloudFlare's optimized zlib,结果非常令人印象深刻。

不幸的是,他们似乎已经假定zlib的开发被放弃了,他们的分叉也没了。我最终能够manually rebase their changes进入current zlib development分支,虽然这对屁股来说真的很痛苦。

无论如何,我还没有能够利用的CloudFlare代码中仍有一个主要优化,即包含fast CRC32 code implemented with the PCLMULQDQ无进位乘法指令更新(Haswell,后来,我相信)英特尔处理器,因为:

  1. 我在Mac上,并且clang集成汇编程序和Apple的古代GAS都不了解使用的新GAS助记符, 以及

  2. 代码从Linux内核中取出并且是GPL2,这使得整个库GPL2,从而基本上使它无法用于我的目的。

  3. 所以我做了一些狩猎,几个小时之后,我偶然发现了一些Apple在他们的bzip2中使用的代码:arm64x86_64的手写,矢量化CRC32实现。

    奇怪的是,x86_64程序集的注释(仅)在arm64源代码中,但它似乎表明此代码可以与zlib一起使用:

    This function SHOULD NOT be called directly. It should be called in a wrapper
    function (such as crc32_little in crc32.c) that 1st align an input buffer to 16-byte (update crc along the way),
    and make sure that len is at least 16 and SHOULD be a multiple of 16.
    

    但不幸的是,经过几次尝试,此时我似乎有点过头了。而且我不确定实际上是如何做到的。所以我希望有人能告诉我如何/在何处调用所提供的功能。

    (如果有一种方法可以在运行时检测到必要的功能,并且如果硬件功能不可用则可以回退到软件实现,那也是很棒的,所以我不会这样做分发多个二进制文件。但是,至少,如果有人可以帮我解决如何让库正确使用基于Apple PCLMULQDQ的CRC32,那么无论如何都会有很长的路要走。)

1 个答案:

答案 0 :(得分:4)

正如它所说,你需要计算一个长度为16字节倍数的16字节对齐缓冲区的CRC和。因此,您将当前缓冲区指针转换为uintptr_t,并且只要其4个LSB位不为零,就会增加将字节输入普通CRC-32例程的指针。一旦你在16字节对齐的地址,你将剩余的长度向下舍入到16的倍数,然后将这些字节提供给快速CRC-32,并将剩余的字节再次提供给慢速计算。

类似的东西:

// a function for adding a single byte to crc
uint32_t crc32_by_byte(uint32_t crc, uint8_t byte);

// the assembly routine
uint32_t _crc32_vec(uint32_t crc, uint8_t *input, int length);

uint32_t crc = initial_value;
uint8_t *input = whatever;
int length = whatever; // yes, the assembly uses *int* length.

assert(length >= 32); // if length is less than 32 just calculate byte by byte
while ((uintptr_t)input & 0xf) { // for as long as input is not 16-byte aligned
    crc = crc32_by_byte(crc, *input++);
    length--;
}

// input is now 16-byte-aligned
// floor length to multiple of 16
int fast_length = (length >> 4) << 4;
crc = _crc32_vec(crc, input, fast_length);

// do the remaining bytes
length -= fast_length;
while (length--) {
    crc = crc32_by_byte(crc, *input++)
}
return crc;