_mm_crc32_u64定义不清楚。

9

为什么世界上会这样定义_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))

好的,我会控制一下语气,但是这段代码的“所有者”是微软,你上一次成功联系微软是什么时候?无论如何,这不是一个“尝试”的问题——内在的部分是有效的,上面的代码也是有效的。问题在于我需要最大的性能,而内在的部分不允许这样做,而且没有什么好的理由。关于“为什么(itA)要这样定义?”这个问题是修辞性的——它应该被定义得不同。我发帖的目的是想看看是否有人测试过正确的代码,经过多平台测试。 - David I. McIntosh
虽然我能写代码,但我无法在所有可能使用我的代码的平台上进行测试,因此我希望比我更擅长低级编程的人有一些有用的代码。 - David I. McIntosh
实际上你问的是“谁写的”,而不是“为什么要这样写”。我从未尝试联系微软,因为我在工作中不使用任何微软产品;但是,你联系过吗? - Sebastian Mach
还有,这个网站是否禁止幽默?您删除了我的原始语句“在你的脑海中,你应该听到小调‘这些东西中有一个不同,这些东西中有一个不属于这里’”,但没有用任何东西替换它来传达相同的意思,我认为您不应该这样做。如果您要编辑别人的帖子,至少不要改变帖子中的重要观点。 - David I. McIntosh
最后,请指出我的帖子中哪里“夸张”了?是我说CRC32始终是32位结果的陈述吗?还是我提出的宏之一与其他宏不一致的说法?上面哪个陈述是不准确的? - David I. McIntosh
显示剩余11条评论
2个回答

12

提供的4个固有函数确实可以充分利用Intel定义的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;
    }

2
不行。注意你对total4的声明与对total1、total2和total3的声明不同。如果我们要混合使用_mm_crc32_u64、_mm_crc32_u32、_mm_crc32_u16和_mm_crc32_u8,我们需要在使用_mm_crc32_u64和其他所有函数之间进行数据类型转换。尽管这些转换很简单,但它们完全是不必要的 - 正如我所说,使用64位目标数据类型是没有意义的。 - David I. McIntosh
更具体地说,给定 const uint8_t *data; unsigned long total = 0xFFFFFFFFUL; int nSize = sizeof input data;,我可以这样做://将内存对齐到4字节边界 for(; nSize>0 && (data&3)!=0; --nSize) total = _mm_crc32_u8(total, *data++); for( ; nSize>=4; nSize -= 4 ) total = _mm_crc32_u32(total, *(reinterpret_cast(data))++); if( nSize>=2 ) { total = _mm_crc32_u16(total, *(reinterpret_cast(data))++); nSize -=2; } if( nSize>0 ) total = _mm_crc32_u8(total, *data++); - David I. McIntosh
但我无法这样做:for(; nSize>0 && (data&3)!=0; --nSize) total = _mm_crc32_u8 (total, *data++); for( ; nSize>=8; nSize -= 8 ) total = _mm_crc32_u64(total, *(reinterpret_cast(data))++); if( nSize>=4 ) { total = _mm_crc32_u32(total, *(reinterpret_cast(data))++); nSize -= 4; } if( nSize>=2 ) { total = _mm_crc32_u16(total, *(reinterpret_cast(data))++); nSize -=2; } if( nSize>0 ) total = _mm_crc32_u8(total, *data++); - David I. McIntosh
在将我的32位“total”转换为64位“total64”的第一个for循环之前,不需要产生任何费用,这是完全不必要和愚蠢的。即64位循环需要是:for( ; nSize>=8; nSize -= 8 ) total = _mm_crc32_u64(total, *(reinterpret_cast<const uint64_t* &>(data))++)&0xFFFFFFFF;并且还有从32位到64位的_mm_crc32_u64的第一个参数的隐式转换。 - David I. McIntosh
@DavidI.McIntosh:你认为这种情况为什么会有任何成本吗?x86-64免费进行零扩展,因此除非您的编译器在优化方面很糟糕,否则对于累加器/ retval的64位类型没有实际成本。(编译器可能不知道高32位为零,但如果您明确编写了“1 +(uint64_t)(uint32_t)retval”,它可能会花费一条指令进行零扩展。通常会反转结果进行后处理,然后将其存储到内存中。 - Peter Cordes
顺便说一下,这个循环缺少前/后处理,因此实际上并没有计算CRC32C。请参阅Mark Adler的在软件中实现SSE 4.2的CRC32C - Peter Cordes

4

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

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

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

查看 i_crc32() 指令。 (遗憾的是,英特尔的 SSE 内部规范中还有更多缺陷,有关其他指令的缺陷示例,请参见 this page


非常感谢。这正是我需要的东西!我会查看并确定它是否符合我的需求。再次感谢。 - David I. McIntosh
你的头文件有注释“(是的,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 );。谢谢。 - David I. McIntosh
3
基本上,x64 crc32指令使用64位通用寄存器作为操作数,将高32位作为结果保留为0,只有低32位包含有效数据。在内嵌函数中,将返回类型设为"__int64",因为实际汇编指令中的结果以64位通用寄存器返回。 - cottonvibes
代码已不再能够随意浏览,因为Google Code已经有效关闭。也许您可以将相关部分添加到您的答案中。 - jww
如果有人遇到这个问题,这是存储库的归档地址:https://code.google.com/archive/p/sse-intrinsics/ - Joshua Estes

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