_mm_crc32_u64 定义不明确

Posted

技术标签:

【中文标题】_mm_crc32_u64 定义不明确【英文标题】:_mm_crc32_u64 poorly defined 【发布时间】:2013-03-23 01:52:15 【问题描述】:

为什么_mm_crc32_u64(...) 是这样定义的?

unsigned int64 _mm_crc32_u64( unsigned __int64 crc, unsigned __int64 v );

“crc32”指令总是累积一个 32 位 CRC,从不一个 64 位 CRC(毕竟,CRC32 不是 CRC64)。如果机器指令 CRC32 碰巧有一个 64 位的目标操作数,则高 32 位将被忽略,并在完成时用 0 填充,因此永远没有 64 位的目标。我理解为什么 Intel 允许在指令上使用 64 位目标操作数(为了统一),但是如果我想快速处理数据,我想要一个尽可能大的源操作数(即,如果我还有那么多数据,那就是 64 位,尾端更小)并且始终是 32 位目标操作数。但是内在函数不允许 64 位源和 32 位目标。注意其他内在函数:

unsigned int _mm_crc32_u8 ( unsigned int crc, unsigned char v ); 

"crc" 的类型不是 8 位类型,返回类型也不是,它们是 32 位。为什么没有

unsigned int _mm_crc32_u64 ( unsigned int crc, unsigned __int64 v );

? Intel 指令支持这一点,that 是最有意义的内在函数。

有没有人有可移植的代码(Visual Studio 和 GCC)来实现后者的内在函数?谢谢。 我的猜测是这样的:

#define CRC32(D32,S) __asm__("crc32 %0, %1" : "+xrm" (D32) : ">xrm" (S))

对于 GCC,并且

#define CRC32(D32,S) __asm  crc32 D32, S 

对于 VisualStudio。不幸的是,我对约束的工作原理知之甚少,对汇编级编程的语法和语义也缺乏经验。

小修改:注意我定义的宏:

#define GET_INT64(P) *(reinterpret_cast<const uint64* &>(P))++
#define GET_INT32(P) *(reinterpret_cast<const uint32* &>(P))++
#define GET_INT16(P) *(reinterpret_cast<const uint16* &>(P))++
#define GET_INT8(P)  *(reinterpret_cast<const uint8 * &>(P))++


#define DO1_HW(CR,P) CR =  _mm_crc32_u8 (CR, GET_INT8 (P))
#define DO2_HW(CR,P) CR =  _mm_crc32_u16(CR, GET_INT16(P))
#define DO4_HW(CR,P) CR =  _mm_crc32_u32(CR, GET_INT32(P))
#define DO8_HW(CR,P) CR = (_mm_crc32_u64((uint64)CR, GET_INT64(P))) & 0xFFFFFFFF;

注意最后一个宏语句有多么不同。缺乏统一性当然表明内在的定义没有得到合理的定义。虽然没有必要在最后一个宏中加入显式的(uint64) 强制转换,但它是隐式的并且确实会发生。反汇编生成的代码显示了 32->64 和 64->32 的代码,这两者都是不必要的。

换一种说法,它是_mm_crc32_u64不是 _mm_crc64_u64,但他们已经实现了它,就好像它是后者一样。

如果我可以正确地得到上面CRC32 的定义,那么我想将我的宏更改为

#define DO1_HW(CR,P) CR = CRC32(CR, GET_INT8 (P))
#define DO2_HW(CR,P) CR = CRC32(CR, GET_INT16(P))
#define DO4_HW(CR,P) CR = CRC32(CR, GET_INT32(P))
#define DO8_HW(CR,P) CR = CRC32(CR, GET_INT64(P))

【问题讨论】:

Does anyone have portable code (Visual Studio and GCC) to implement the latter intrinsic? Thanks. --> 你试过什么??? ... ??????????请注意,“8-bits”不是一种类型。 请注意,没有必要那么粗鲁。如果您比您所呼唤的“愚蠢”人更“聪明”(如“谁是定义的“聪明”人”):您为什么不尝试联系代码的版权所有者? 好的,我会低调一点,但代码的“所有者”是 Microsoft,您上次成功联系 Microsoft 是什么时候?无论如何,这不是真正“尝试”某事的问题 - 内在工作,上面的代码工作。问题是我需要最大的性能,而内在不允许这样做,而且没有充分的理由。问题“为什么(itA)被这样定义?”是修辞 - 它应该有不同的定义。我的帖子的重点是看看是否有人测试过代码以正确执行,代码已经过多平台测试。 虽然我可以编写代码,但我无法在人们可能使用我的代码的所有平台上对其进行测试,因此我希望在低级编程方面比我更擅长的人有一些有用的代码. 其实你问的是“谁写的”,而不是“为什么这样写”。而且我从来没有尝试过联系微软,因为我没有使用任何微软的产品来工作;但是,你呢? 【参考方案1】:

提供的 4 个内在函数确实允许英特尔定义的 CRC32 指令的所有可能用途。指令输出始终为 32 位,因为指令被硬编码为使用特定的 32 位 CRC 多项式。但是,该指令允许您的代码一次向其提供 8、16、32 或 64 位的输入数据。一次处理 64 位应最大限度地提高吞吐量。如果仅限于 32 位构建,则一次处理 32 位是最好的。如果输入字节数是奇数或不是 4/8 的倍数,一次处理 8 或 16 位可以简化代码逻辑。

#include <stdio.h>
#include <stdint.h>
#include <intrin.h>

int main (int argc, char *argv [])
    
    int index;
    uint8_t *data8;
    uint16_t *data16;
    uint32_t *data32;
    uint64_t *data64;
    uint32_t total1, total2, total3;
    uint64_t total4;
    uint64_t input [] = 0x1122334455667788, 0x1111222233334444;

    total1 = total2 = total3 = total4 = 0;
    data8  = (void *) input;
    data16 = (void *) input;
    data32 = (void *) input;
    data64 = (void *) input;

    for (index = 0; index < sizeof input / sizeof *data8; index++)
        total1 = _mm_crc32_u8 (total1, *data8++);

    for (index = 0; index < sizeof input / sizeof *data16; index++)
        total2 = _mm_crc32_u16 (total2, *data16++);

    for (index = 0; index < sizeof input / sizeof *data32; index++)
        total3 = _mm_crc32_u32 (total3, *data32++);

    for (index = 0; index < sizeof input / sizeof *data64; index++)
        total4 = _mm_crc32_u64 (total4, *data64++);

    printf ("CRC32 result using 8-bit chunks: %08X\n", total1);
    printf ("CRC32 result using 16-bit chunks: %08X\n", total2);
    printf ("CRC32 result using 32-bit chunks: %08X\n", total3);
    printf ("CRC32 result using 64-bit chunks: %08X\n", total4);
    return 0;
    

【讨论】:

不。请注意,您对 total4 的声明与对 total1、total2 和 total3 的声明不同。如果我们要混合使用_mm_crc32_u64、_mm_crc32_u32、_mm_crc32_u16和_mm_crc32_u8,我们需要在_mm_crc32_u64和所有其他使用之间进行数据类型转换。诚然,它们是微不足道的,但它们也完全没有必要——正如我所说,使用 64 位目标数据类型没有意义。 更具体地说,给定const uint8_t *data; unsigned long total = 0xFFFFFFFFUL; int nSize = sizeof input data;,我可以这样做://Align memory on 4-byte boundary for(; nSize&gt;0 &amp;&amp; (data&amp;3)!=0; --nSize) total = _mm_crc32_u8(total, *data++); for( ; nSize&gt;=4; nSize -= 4 ) total = _mm_crc32_u32(total, *(reinterpret_cast&lt;const uint32_t* &amp;&gt;(data))++); if( nSize&gt;=2 ) total = _mm_crc32_u16(total, *(reinterpret_cast&lt;const uint16_t* &amp;&gt;(data))++); nSize -=2; if( nSize&gt;0 ) total = _mm_crc32_u8(total, *data++); 但我不能这样做:for(; nSize&gt;0 &amp;&amp; (data&amp;3)!=0; --nSize) total = _mm_crc32_u8 (total, *data++); for( ; nSize&gt;=8; nSize -= 8 ) total = _mm_crc32_u64(total, *(reinterpret_cast&lt;const uint64_t* &amp;&gt;(data))++); if( nSize&gt;=4 ) total = _mm_crc32_u32(total, *(reinterpret_cast&lt;const uint32_t* &amp;&gt;(data))++); nSize -= 4; if( nSize&gt;=2 ) total = _mm_crc32_u16(total, *(reinterpret_cast&lt;const uint16_t* &amp;&gt;(data))++); nSize -=2; if( nSize&gt;0 ) total = _mm_crc32_u8(total, *data++); 在将我的 32 位“total”转换为 64 位“total64”的第一个 for 循环之前不会产生成本,这完全没有必要而且很愚蠢。 IE。 64 位循环需要是:for( ; nSize&gt;=8; nSize -= 8 ) total = _mm_crc32_u64(total, *(reinterpret_cast&lt;const uint64_t* &amp;&gt;(data))++)&amp;0xFFFFFFFF; 并且还有第一个参数到 _mm_crc32_u64 从 32 位到 64 位的隐式转换。 @DavidI.McIntosh:你认为这个案子为什么要付出任何代价? x86-64 零扩展是免费的,所以除非你的编译器在优化方面很烂,否则 64 位类型的累加器 / retval 没有实际成本。 (编译器可能不“知道”高 32 位为零。但这仅在您明确编写 1 + (uint64_t)(uint32_t)retval 时才重要,它可能会花费一条指令进行零扩展。通常只会将结果反转为后处理,然后将其存储到内存中。【参考方案2】:

是否有人有可移植代码(Visual Studio 和 GCC)来实现后者的内在函数?谢谢。

我和我的朋友编写了一个 c++ sse 内在函数包装器,其中包含 crc32 指令与 64 位 src 的更优选用法。

http://code.google.com/p/sse-intrinsics/

参见 i_crc32() 指令。 (遗憾的是,英特尔的 sse 内在规范在其他指令上存在更多缺陷,请参阅 this page 了解更多内在设计缺陷示例)

【讨论】:

非常感谢。这正是我一直在寻找的东西!我会看看它是否给了我我需要的东西。再次感谢。 您的头文件有注释“(是的,64 位 CRC32 生成有效的 32 位结果)”。您是说 VisualStudio 头文件中的声明 unsigned __int64 _mm_crc32_u64( unsigned __int64 crc, unsigned __int64 v ); 不正确和/或具有误导性吗?因为我注意到您对 _mm_crc32_u64 内在函数的使用就像我声称它应该被声明的那样,即好像它是 unsigned __int32 _mm_crc32_u64( unsigned __int32 crc, unsigned __int64 v );。谢谢。 基本上 x64 crc32 指令使用 64 位 gpr 寄存器作为操作数,将结果的高 32 位保留为 0,只有低 32 位包含合法数据。内部函数中的返回类型为“__int64”,因为结果在实际 asm 指令中以 64 位 gpr 的形式返回。 该代码不再可用于随意浏览,因为 Google 代码实际上已关闭。也许您可以将相关部分添加到您的答案中。

以上是关于_mm_crc32_u64 定义不明确的主要内容,如果未能解决你的问题,请参考以下文章

在 Intel CPU 上选择 32 位和 64 位固有 CRC

AVX2 上的 256 位 CRC 计算

我应该在 64 位版本中同时定义 _WIN32 和 _WIN64 吗?

是否有一种定义明确且高性能的方式将双精度位转换为 uint64_t 并返回

_WIN64 未在 x64 项目中定义

#define S64_MIN 在 linux 数据类型中是如何定义的?