在 Python/Cython/Numpy 中计算 2 个二进制向量之间汉明距离的最快方法

问题描述

我正在尝试计算二进制向量和二进制向量矩阵之间的汉明距离。我能找到的最快方法是在 Numpy 中使用无符号 8 位整数:

import numpy as np
np.count_nonzero(data[0] !=  data,axis=1)

然而,这种方法的问题在于它首先找到所有不同的位,然后对不同的数量求和。这会不会有点浪费?我尝试在 C++ 中实现一个基本版本,其中我还记录了不同的位数,这样在最后不需要总和,但这要慢得多。可能是因为 Numpy 使用 SIMD 指令。

所以我的问题是。有没有办法在Numpy/Python/Cython中使用SIMD指令直接计算汉明距离?

解决方法

理想情况下,您真正​​希望 CPU 执行的操作是 sum += popcount( a[i] ^ b[i]) 块尽可能大。例如在 x86 上,通过一条指令一次使用 AVX2 对 32 个字节进行异或,然后再使用一些指令(包括 vpshufb 和 vpaddq)将计数累积到每个元素计数的 SIMD 向量中(最后水平求和)。

对于特定 ISA(例如 x86-64),使用 C++ 内在函数很容易做到这一点。

您可以使用 std::bitset<64> 将 64 位块异或在一起,并使用 .count() 作为高效 popcount 的可移植 API 来接近可移植代码。 Clang 可以将标量 popcount 自动向量化为 AVX2,但 GCC 不能。

为了在不违反严格别名的情况下安全地构造它,您可能需要将其他类型的任意数据 memcpy 转换为 unsigned long long


我不知道 Numpy 是否有一个用于编译的循环,否则您可能需要在一次传递中进行异或,然后在另一传递中执行 popcount,这会降低计算强度,因此您肯定想要缓存阻止它在您返回重新读取它们之前,将它们分成在 L1d 缓存中保持热的小块。