_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>0 && (data&3)!=0; --nSize) total = _mm_crc32_u8(total, *data++); for( ; nSize>=4; nSize -= 4 ) total = _mm_crc32_u32(total, *(reinterpret_cast<const uint32_t* &>(data))++); if( nSize>=2 ) total = _mm_crc32_u16(total, *(reinterpret_cast<const uint16_t* &>(data))++); nSize -=2; if( nSize>0 ) total = _mm_crc32_u8(total, *data++);
但我不能这样做: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<const uint64_t* &>(data))++); if( nSize>=4 ) total = _mm_crc32_u32(total, *(reinterpret_cast<const uint32_t* &>(data))++); nSize -= 4; if( nSize>=2 ) total = _mm_crc32_u16(total, *(reinterpret_cast<const uint16_t* &>(data))++); nSize -=2; if( nSize>0 ) total = _mm_crc32_u8(total, *data++);
在将我的 32 位“total”转换为 64 位“total64”的第一个 for 循环之前不会产生成本,这完全没有必要而且很愚蠢。 IE。 64 位循环需要是:for( ; nSize>=8; nSize -= 8 ) total = _mm_crc32_u64(total, *(reinterpret_cast<const uint64_t* &>(data))++)&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
我应该在 64 位版本中同时定义 _WIN32 和 _WIN64 吗?