【问题标题】:Rewrite the code without branches重写代码没有分支
【发布时间】:2015-09-11 01:10:45
【问题描述】:

我有以下功能:

uint16_t foo(uint8_t input)
{
     uint16_t N = 38;
     if (!(input&1))
     {
       N = 0;
     }
     else 
     {
        if ((input&2) >> 1)
        {
           N = ~N;
        }
     }
     return N;
}

我想在不使用ifs 的情况下重写它,就像一个内联函数,将38 转换为0,3865497,给定input,并且只使用标准C 位旋转操作。

关键不是编译器可以内联函数,或者函数要快,而只是为了摆脱分支并保持恒定时间,而不管input 是什么。

第一个if很简单:

uint16_t c = ((input&1)^1)-1;
N &= c;

但我很难找到一些简单的方法来进行条件否定。

【问题讨论】:

  • 那你的目标是什么?基本上,任何在 C 语言之后诞生的 ISA 至少有一种方法可以做到x=cond?y:z 快,真的快。
  • 我的目标是 MCU - MSP430,它的指令集非常有限。不管input 是什么,关键不是要快,而是要保持恒定时间。
  • 用全 0 或全 1 的掩码进行异或运算的条件补码:N = N ^ (0 - ((input&2) >> 1))
  • 如果您陈述您所面临的实际问题,您可能会得到更好的答案。我怀疑这里有XY-problem

标签: c optimization bit-manipulation inline


【解决方案1】:

7 次操作,没有乘法,基于 njuffa 的评论:

uint16_t bar(uint8_t input)
{
    return (0-(input&1)) & (38^(0-((input>>1)&1)));
}

Demo

【讨论】:

  • 感谢您的回答。所有的答案都很棒,但我接受了你的答案,因为它与我最后使用的代码几乎相同。
【解决方案2】:

这应该可行。

#include<stdio.h>

int main(){
    int input;
    scanf("%d", &input);
    int N = (input&1)*(((input&2)*32729)+(38+((input&2)>>1)));
    printf("%d\n", N);
}

【讨论】:

  • 感谢您的回答。这里的问题是乘以32729。我只需要继续使用按位操作。
【解决方案3】:

为了保证运行时和平衡的代码,必须使用汇编程序。由于 MSP430 汇编器没有那么复杂,所以这不是什么大问题。请注意,诸如乘法之类的事情很可能由具有恒定运行时间的函数执行。

避免分支是没有意义的。相反,您应该平衡执行路径,例如使用 NOP(无操作)。 MSP430 用户指南包括指令时序。请注意,时序是固定的,这与 ARM 等较大的 CPU 不同,后者取决于管道和内存时序。

一般说明:通过 CPU 周期进行计时通常是个坏主意。像 MSP430 这样的现代 MCU 提供内部定时器和与 ADC 等其他外设的连接以触发,例如具有高度准确的时序转换。它们可以生成中断,以便 CPU 可以准备下一个值或读取样本,而无需关心代码的运行时间(除非花费的时间太长)。

使用 CPU 来禁止例如中断,因为这会破坏任何时间。这使得维护这样的系统成为一场噩梦。

【讨论】:

  • 谢谢!感谢您对中断的说明。我实际上意识到我忘了关掉它们:)
【解决方案4】:

如果我以正确的方式阅读您的代码,它会显示以下内容:

uint16_t foo1(uint8_t input)
{
     uint16_t N = 38;

     uint16_t bit0 = input & 1;
     uint16_t nbit0 = bit0 ^ 1;
     uint16_t bit1 = (input & 2) >> 1;
     uint16_t nbit1 = bit1 ^ 1;

     N = nbit0 * ( bit1 * ~N + nbit1 * N);

     return N;
}

随意摆脱变量。它们只是为了便于阅读。

【讨论】:

  • 嗯,看起来不错,唯一的问题是那些乘法,我认为可以使用ANDs 和一些 0 减 1 技巧来处理。
  • 是的。或者通过有符号移位。
  • 实际上,如果您设法将bit1 作为全零或全一字,您可以将bit1 * ~N + nbit1 * N 替换为N^bit1
【解决方案5】:

使用表查找。

uint16_t foo(uint8_t input) {
    int index= input & 0x3;
    const uint16 table[4]= {0,~38,0,38};
    return table[index];
}

【讨论】:

  • 优秀的解决方案 - 但我建议你摆脱int index = input &amp; 0x3并将return语句更改为return table[input &amp; 0x3];
  • 这也不错,想的更底层一点,我可以把这些值放在堆栈中,然后通过index 寻址它们。
  • @NumberFour 将这些数字放在堆栈上会减慢代码速度,因为这样做需要在访问它们之前将每个数字复制到堆栈中(每个驻留的函数至少一次)并且不会避免不得不从某个地方复制它们(包括编码成操作码的数字)。
  • @BobJarvis 你的建议应该为任何编译器生成与上述相同的汇编代码,除非优化被关闭。
猜你喜欢
  • 1970-01-01
  • 2022-11-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-22
  • 1970-01-01
  • 2014-12-21
相关资源
最近更新 更多