异构元组的快速 8 位校验和算法
Fast 8-bit checksum algorithm for heterogenous tuples
假设我有包含 3 个异构整数类型(int16_t
、int32_t
、int64_t
)的三元组,我想为这 3 个值计算一个 8 位无符号校验和。假设所有值在所有有效位上均匀分布,因此我们不能通过在连接它们时截断任何值来作弊。
计算具有相对较低冲突率和非加密属性的校验和的快速方法是什么?我猜我可以连接字节并使用 Fletcher 的校验和或 Pearson 散列的变体,但我看到的所有这些实现似乎都过时了,我想看看我是否可以进一步利用任何 SIMD 或属性现代 (Skylake) 架构。
我也知道 MurmurHash,但它没有 8 位实现。
现代 x86 非常快 CRC32C (hardware instruction added in SSE4.2)。将 int32 和 int16 连接成 zero-extended int64_t,并使用两条 CRC32C 指令来累积单个校验和,您可能会得到很好的结果。要让编译器为您执行此操作,请使用 imintrin.h 中的内部函数:unsigned __int64 _mm_crc32_u64( unsinged __int64 crc, unsigned __int64 data )
.
根据 Agner Fog's instruction tables,crc32
在 Skylake 上每个时钟吞吐量为 1,延迟为 3 个周期,因此将其输入 2x 8 字节并获得 32 位结果应该只需要 2 uops / 6周期延迟。首先将 uint64_t
提供给它,以便连接 uint16 和 uint32 脱离关键路径,即在 shift/or 和第一个 crc32
.
之间创建 instruction-level 并行性
然后将crc32c水平异或到8位:
uint32_t crc = my_object_crc32(&my_object);
crc ^= crc>>16;
crc ^= crc>>8;
crc = (uint8_t)crc;
将更宽的 crc/散列/校验和的位混合成一个 8 位值的水平异或适用于您要使用的任何散列函数。
或者干脆取CRC32C的低字节。 IDK 将所有 4 个字节异或为 1 得到多少(如果有的话)。同样,任何 multi-byte 哈希函数都可行。
您甚至可以水平异或输入中的所有字节。例如加载 16 字节的 SSE2 负载,屏蔽填充字节,然后 pshufd
/ pxor
减少到 8 个字节,pshuflw
/ pxor
减少到 4 个字节。
然后另一个 pshuflw
/ pxor
向下到 2 个字节,并且 movd
到整数以进行最后的移位/异或。 (或者你可以更早地 movd
到整数,特别是如果编译器有 BMI2 rorx
到 copy-and-shift 一条指令)。
既然你提到所有的值都均匀分布在你的所有位上,你可以简单地选择元组中的 any 字节作为你的 8 位哈希,忽略剩余的位,这基本上是免费的。结果是一个完美统一的哈希函数,这是最好的(它的碰撞概率为 256 分之一,这是不可预测输入的下限)。
如果您输入的位以某种方式 non-uniform,您只需要一个 "better" 哈希函数(在绝大多数情况下,真实数据不仅是随机数,而且我猜你的情况不一样)。
假设我有包含 3 个异构整数类型(int16_t
、int32_t
、int64_t
)的三元组,我想为这 3 个值计算一个 8 位无符号校验和。假设所有值在所有有效位上均匀分布,因此我们不能通过在连接它们时截断任何值来作弊。
计算具有相对较低冲突率和非加密属性的校验和的快速方法是什么?我猜我可以连接字节并使用 Fletcher 的校验和或 Pearson 散列的变体,但我看到的所有这些实现似乎都过时了,我想看看我是否可以进一步利用任何 SIMD 或属性现代 (Skylake) 架构。
我也知道 MurmurHash,但它没有 8 位实现。
现代 x86 非常快 CRC32C (hardware instruction added in SSE4.2)。将 int32 和 int16 连接成 zero-extended int64_t,并使用两条 CRC32C 指令来累积单个校验和,您可能会得到很好的结果。要让编译器为您执行此操作,请使用 imintrin.h 中的内部函数:unsigned __int64 _mm_crc32_u64( unsinged __int64 crc, unsigned __int64 data )
.
根据 Agner Fog's instruction tables,crc32
在 Skylake 上每个时钟吞吐量为 1,延迟为 3 个周期,因此将其输入 2x 8 字节并获得 32 位结果应该只需要 2 uops / 6周期延迟。首先将 uint64_t
提供给它,以便连接 uint16 和 uint32 脱离关键路径,即在 shift/or 和第一个 crc32
.
然后将crc32c水平异或到8位:
uint32_t crc = my_object_crc32(&my_object);
crc ^= crc>>16;
crc ^= crc>>8;
crc = (uint8_t)crc;
将更宽的 crc/散列/校验和的位混合成一个 8 位值的水平异或适用于您要使用的任何散列函数。
或者干脆取CRC32C的低字节。 IDK 将所有 4 个字节异或为 1 得到多少(如果有的话)。同样,任何 multi-byte 哈希函数都可行。
您甚至可以水平异或输入中的所有字节。例如加载 16 字节的 SSE2 负载,屏蔽填充字节,然后 pshufd
/ pxor
减少到 8 个字节,pshuflw
/ pxor
减少到 4 个字节。
然后另一个 pshuflw
/ pxor
向下到 2 个字节,并且 movd
到整数以进行最后的移位/异或。 (或者你可以更早地 movd
到整数,特别是如果编译器有 BMI2 rorx
到 copy-and-shift 一条指令)。
既然你提到所有的值都均匀分布在你的所有位上,你可以简单地选择元组中的 any 字节作为你的 8 位哈希,忽略剩余的位,这基本上是免费的。结果是一个完美统一的哈希函数,这是最好的(它的碰撞概率为 256 分之一,这是不可预测输入的下限)。
如果您输入的位以某种方式 non-uniform,您只需要一个 "better" 哈希函数(在绝大多数情况下,真实数据不仅是随机数,而且我猜你的情况不一样)。