作为对 chux 方法的修改,查找表可以替换为向量移位和掩码。这是一个使用 GCC's vector extensions 的示例。
#include <stdint.h>
#include <stddef.h>
typedef uint8_t vec8x8 __attribute__((vector_size(8)));
void sumit(uint8_t number_of_buf,
uint8_t k,
const uint8_t buf[number_of_buf][k],
vec8x8 * restrict sums) {
static const vec8x8 shift = {0,1,2,3,4,5,6,7};
for (size_t i = 0; i < k; i++) {
sums[i] = (vec8x8){0};
for (size_t buf_index = 0; buf_index < number_of_buf; buf_index++) {
sums[i] += (buf[buf_index][i] >> shift) & 1;
}
}
}
Try it on godbolt.
我交换了 chux 的答案中的循环,因为一次累积一个缓冲区索引的总和似乎更自然(然后可以将总和缓存在整个内部循环的寄存器中)。缓存性能可能会有所折衷,因为我们现在必须以列优先顺序读取二维 buf 的元素。
以ARM64为例,GCC 11.1编译内循环如下。
// v1 = sums[i]
// v2 = {0,-1,-2,...,-7} (right shift is done as left shift with negative count)
// v3 = {1,1,1,1,1,1,1,1}
.L4:
ld1r {v0.8b}, [x1] // replicate buf[buf_index][i] to all elements of v0
add x0, x0, 1
add x1, x1, x20
ushl v0.8b, v0.8b, v2.8b // shift
and v0.8b, v0.8b, v3.8b // mask
add v1.8b, v1.8b, v0.8b // accumulate
cmp x0, x19
bne .L4
我认为一次执行两个字节会更有效(因此将 i 上的循环展开 2 倍)并使用 128 位向量操作。我把它留作练习:)
我不清楚这最终会比查找表快还是慢。您可能必须在感兴趣的目标机器上对两者进行概要分析。