高效的微小布尔矩阵乘法

时间:2018-01-10 16:15:56

标签: c matrix boolean

我有一些无符号的16位整数s,我希望映射到无符号的32位整数r,使s中的每个翻转位都翻转r中最多只有一个(给定)位 - 只是0..160..32之间的映射。所以我们可以将其视为矩阵方程

Ps = r

其中P是32 x 16布尔矩阵,s16 x 1布尔向量,r32 x 1布尔向量。我有一种直觉,认为存在一些我失踪的超级简单黑客。重要提示:目标机器是16位mcu!

这是我能做的最好的事情:

static u16 P[32] = someArrayOrWhatever();

u32 FsiPermutationHack(u16 s) {
    u32 r;
    for (u16 i = 0; i < 32; i++)
    {
            r |= ((u32)((P[i] & s) > 0) << i);
    }
    return r;
}

理由是这样的:r的第i位是1,当且仅当(P[i] & s) != 0x0000时。我太愚蠢了,不能拆解东西,但我猜这将是〜100条说明如果我们没有做那个愚蠢的u32演员。但话说回来,也许编译器会自动将循环拆分为两个,在这种情况下,它对我们来说看起来非常好。

为切线道歉,只是想我分享我的尝试解决方案 - 你有更好的解决方案吗?

1 个答案:

答案 0 :(得分:3)

因为你说,

  

我猜这将是~100指令,如果我们没有   做那个愚蠢的u32演员。但话说回来,也许是编译器   为我们自动拆分循环,在这种情况下,它看起来很漂亮   对我们有好处。

  

我有一种直觉,认为存在一些我缺少的超级简单黑客

,我将解释你是如何在这个用于16位处理器的代码中尽量减少使用32位算法的。

你真的应该学习如何反汇编并检查编译结果,看看编译器是否会在你假设的情况下自动拆分循环,但假设它没有,我不明白为什么你不能这样做手动:

static u16 P[32];  /* value assigned elsewhere */

u32 FsiPermutationHack(u16 s) {
    u16 *P_hi = P + 16;
    u16 r_lo = 0;
    u16 r_hi = 0;

    for (u16 i = 0; i < 16; i++) {
        r_lo |= (P[i] & s) != 0) << i;
        r_hi |= (P_hi[i] & s) != 0) << i;
    }

    return ((u32) r_hi << 16) + r_lo;
}

假设u16u32是无符号的16位和32位(分别)整数,没有填充位。

另请注意,使用u16类型代替u32执行算术的想法应该是一种改进,假设类型u32的整数提升等级高于unsigned int。粗略地说,这归结为实现的unsigned int是16位类型。这对于16位处理器的实现来说是完全合理的。但是,在intunsigned int而不是32位类型的系统上,无论如何,所有较窄的整数算术参数都将被提升为32位。

<强>更新

就更好的替代算法的可能性而言,我观察到结果的每个位都是从数组P的不同元素计算出来的,即每个元素的整个值都被使用,并且该元素size与目标机器的本机字大小相同。似乎没有比数组元素执行更少的16位按位AND运算的余地(但见下文)。

如果我们接受每个数组元素必须单独处理,那么提供的实现可以很好地有效地处理它:

  • 它只执行16位计算,直到汇总最终结果为止;
  • 它在同一个循环中计算结果的上半部分和下半部分,因此只产生16次迭代的循环开销,而不是32
  • 它在很大程度上消除了创建P_hi以访问数组上半部分所需的额外索引算法

可以手动展开循环以节省更多周期,但这是您绝对应该依赖编译器为您执行的优化类型。

至于“bit twddling hacks”,我认为对于任何这种性质的唯一范围是将相邻的16位数组元素处理为32位无符号整数。这将允许执行一个32位按位AND代替每两个16位AND。这将与两个32位比较相结合( vs。上述代码中的两个16位比较)。可以保留上述方法的16位移位和按位OR运算。除了由于违反严格别名规则而导致正式未定义的行为,这将涉及32位算术,大概是16位机器上16位算术的一半。绩效比预测的更好,但我认为没有理由期望从这种方法中取得重大胜利。