三元向量的快速内积

Fast inner product of ternary vectors

本文关键字:向量 三元      更新时间:2023-10-16

考虑两个向量,AB,大小为 n,7 <= n <<em> = 23。AB 都仅由 -1、0 和 1 组成。

我需要一个快速算法来计算 AB 的内积。

到目前为止,我已经考虑使用以下编码将符号和值存储在单独的uint32_t中:

  • 符号 0,值 0 → 0
  • 符号 0,值 1 → 1
  • 符号 1,值 1 → -1。

我想到的C++实现如下所示:

struct ternary_vector {
    uint32_t sign, value;
};
int inner_product(const ternary_vector & a, const ternary_vector & b) {
    uint32_t psign = a.sign ^ b.sign;
    uint32_t pvalue = a.value & b.value;
    psign &= pvalue;
    pvalue ^= psign;
    return __builtin_popcount(pvalue) - __builtin_popcount(psign);
}

这工作得相当好,但我不确定是否有可能做得更好。非常感谢对此事的任何评论。

我喜欢

有 2 uint32_t,但我认为你的实际计算有点浪费

只是几个小点:

  • 我不确定参考(通过const &获得ab)-与将它们放在堆栈上相比,这增加了间接程度。当代码这么小(也许有几个时钟)时,这很重要。尝试按值传递,看看你得到了什么

  • 不幸的是,__builtin_popcount效率非常低。我自己也用过它,但发现即使是我编写的一个非常基本的实现也比这快得多。但是 - 这取决于平台。

基本上,如果平台具有硬件popcount实现,则__builtin_popcount使用它。如果没有 - 它使用非常低效的替代品。

这里的一个严重问题是重用正向量和负向量的psignpvalue变量。以这种方式混淆代码既没有对编译器也没有好处。

您是否可以在std::bitset<2>中编码您的三元状态并根据and定义乘积?例如,如果三元类型为:

 1 = P = (1, 1)
 0 = Z = (0, 0)
-1 = M = (1, 0) or (0, 1)

我相信您可以将他们的产品定义为:

1 *  1 =  1 => P * P = P => (1, 1) & (1, 1) = (1, 1) = P
1 *  0 =  0 => P * Z = Z => (1, 1) & (0, 0) = (0, 0) = Z
1 * -1 = -1 => P * M = M => (1, 1) & (1, 0) = (1, 0) = M

然后内积可以从元素的位开始,然后......我正在研究如何将它们加在一起。

编辑:

我愚蠢的建议没有考虑到(-1)(-1) = 1,这是我提出的代表无法处理的。感谢@user92382提出这个问题。

根据您的架构,您可能希望优化临时位向量 - 例如,如果您的代码要编译为FPGA,或布置到ASIC,那么一系列逻辑操作在速度/能量/面积方面将比存储和读取/写入两个大缓冲区更好。

在这种情况下,您可以执行以下操作:

int inner_product(const ternary_vector & a, const ternary_vector & b) {
   return __builtin_popcount( a.value & b.value & ~(a.sign ^ b.sign))
      -   __builtin_popcount( a.value & b.value &  (a.sign ^ b.sign));  
}

这将很好地布局 - (a.value & b.value & ...)可以启用/禁用XOR门,其输出分成两个有符号的累加器,第一个路径在累积之前被NOT化。