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

8
我最近在尝试使用CloudFlare的优化zlib,结果非常惊人。不幸的是,他们似乎认为zlib的开发已经停止,并且他们的分支已经脱离了。最终,我能够将手动重定基于当前zlib开发分支的更改,尽管这真的很麻烦。无论如何,仍然有一个主要的优化在CloudFlare代码中我还没有能够利用,即使用新的(我相信是Haswell及更高版本)Intel处理器中包含的PCLMULQDQ无进位乘法指令实现的快速CRC32代码,因为:
  1. 我使用Mac,其中clang集成汇编器和苹果古老的GAS都不理解新版GAS助记符的使用方式,

  2. 这段代码来自于Linux内核,遵循GPL2协议,这意味着整个库都是GPL2,基本上无法满足我的需求。

所以我进行了一些搜索,在几个小时后我偶然发现了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,那将大有裨益。

您可以使用CPUID指令在运行时枚举硬件特性。请查阅英特尔的文档。 - James
1个回答

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;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接