我想通过位操作来尝试是值得的。
假设所有数字都是 0 或 1。
然后您可以将两个向量打包成位数组。然后通过以下方式计算内积:
for (int i = 0; i < N; i += 256)
res += popcount(A[i..i+255] & B[i..i+255]);
and 操作自然存在于 AVX/AVX2 中。最难的问题是如何快速计算 YMM 寄存器的 popcount。
现在假设给定 0、1 和 2。对于每个整数向量 A 组成两个位向量 A1 和 A2:
A1[i] = (A[i] >= 1);
A2[i] = (A[i] >= 2);
现在我们可以注意到:
A[i] * B[i] = A1[i] * B1[i] + A1[i] * B2[i] + A2[i] * B1[i] + A2[i] * B2[i];
所以我们可以用下面的伪代码计算内积:
for (int i = 0; i < N; i += 256) {
res += popcount(A1[i..i+255] & B1[i..i+255]);
res += popcount(A2[i..i+255] & B1[i..i+255]);
res += popcount(A1[i..i+255] & B2[i..i+255]);
res += popcount(A2[i..i+255] & B2[i..i+255]);
}
这允许每次迭代处理 256 个元素,但每次迭代会慢 4 倍。有效地,每个操作有 64 个元素。由于 popcount 可能是计算中最慢的部分,我们可以说它需要 N/64 popcount_256 次操作来计算内积。
编辑:我决定为这个想法添加一个小例子:
A = {01212012210}; //input array A
B = {21221100120}; //input array B
A1 = {01111011110}; //A should be stored in two halves like this
A2 = {00101001100};
B1 = {11111100110}; //B is stored in similar two halves
B2 = {10110000010};
A1 & B1 = {01111000110}, popcount = 6; //computing pairwise and-s + popcounts
A1 & B2 = {00110000010}, popcount = 3;
A2 & B1 = {00101000100}, popcount = 3;
A2 & B2 = {00100000000}, popcount = 1;
res = 6 + 3 + 3 + 1 = 13 //summing all the popcounts