【问题标题】:Optimising "i = b ? (i | mask) : (i & ~mask)"优化“i = b ? (i | mask) : (i & ~mask)”
【发布时间】:2017-03-30 12:26:26
【问题描述】:

我希望能够设置或清除 uintX_t t 的(多个)位。

i 是一个运行时变量 (uintX_t)。 b 是一个运行时变量 (uintX_t),它被限制为 01

mask 是编译时常量。

有没有更好的办法:

i = b ? (i | mask) : (i & ~mask)

如果可能的话,我希望避免分支。目标是 ARM,如果重要的话。

【问题讨论】:

  • i = bi == b?
  • @HongOoi i = (b ? (i | mask) : (i & ~mask))

标签: c bit-manipulation ternary-operator


【解决方案1】:

利用-1u 是设置所有位的值这一事实:

i = (i & ~mask) | (mask & -b);

i ^= (i ^ -b) & mask;

第二种方法减少了操作次数和代码大小。第一种方法在超标量架构上可能仍然更快,因为某些操作可以并行执行。

【讨论】:

  • -b-1u 不是一回事。如果b 是任何有符号的小整数类型,您最终会暂时得到一个负数。与-1u 不同,-1u 始终为正数。在这种特定情况下这不会成为问题,但在 (mask & -b) << n 这样的代码中会是灾难性的。
  • @Lundin 对,如果bint 窄并且平台不使用二进制补码,我的答案中的代码实际上存在问题。在这种情况下,需要额外的演员 -(unsigned)b
【解决方案2】:

另一种选择:始终将位设置为 0(左侧),并可选择将位设置为 1(右侧)。

i = (i & ~mask) | (mask * b);

【讨论】:

  • 这也遵循 Guilherme 的回答并使用它或分发和
  • @harold 您的评论是否不小心遗漏了什么?结局似乎很奇怪。
  • 不,我只是没有清楚地格式化它,我的意思是 OR 分布在 AND 上
【解决方案3】:

这里的想法是用乘法替换分支,我们可以根据 b 的值将每一边归零:

i = (i | (mask * b)) & (~mask | (mask * b));

【讨论】:

  • 我用Java测试过,如果你愿意,我可以在java中发布测试代码,表明它确实有效
  • 现在的问题是:在性能损失、分支或乘法方面哪个更糟?
  • 在我的测试中,我使用了 i = 101(二进制),掩码 = 110(二进制)
  • 你可以很容易地发现,添加两个代码并分别运行 100 万次,然后测量时间。
  • 你可以简单地将乘法重写为否定和按位与,以防你的乘法速度很慢(通常值得这样重写)
【解决方案4】:

最易读的方法是分几个步骤执行此操作 - 这样做不会影响性能,但会提高可读性。

与位运算符一样,您必须小心隐式类型提升。例如,不小心使用~ 往往会产生隐式提升错误。 (?: 运算符还通过相互平衡第二个和第三个操作数来默默提升结果。)

可读、可移植、安全的代码:

uintx_t i = ... ;
uintx_t b = ... ;  // 1 or 0

i &= (uintx_t)~mask;   // always clear the bit
i |= mask * b;         // if b is 1, set the bit, otherwise OR with 0

【讨论】:

  • 谢谢,我喜欢这种易读的方法。如果i 是 AVR 上的 PORTA,这会导致 GPIO 故障吗?还是在编译过程中将这两个语句合并为一个?我希望将 PORTA 标记为 volatile 以阻止这种情况发生。
  • @chrisdew 这会导致 GPIO 在几个时钟周期内变低。不允许编译器优化代码,因为PORTA 将是一个易失性寄存器。在某些硬件上,GPIO 端口可能需要几个时钟周期才能稳定。
猜你喜欢
  • 1970-01-01
  • 2012-07-29
  • 1970-01-01
  • 2023-03-09
  • 2021-11-01
  • 2021-04-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多