【问题标题】:What's a good way to assign to a bit with a specific index?使用特定索引分配位的好方法是什么?
【发布时间】:2018-10-03 17:20:17
【问题描述】:

这个问题:

How do you set, clear, and toggle a single bit?

讨论了对较大值内特定位的三个操作,它们以直接的方式对应于该位上的 OR 1、AND 0 和 XOR 1。但是,如果我们事先不知道第二位的值怎么办?如果我们想要执行一个赋值,而我们在编译时不知道另一个操作数位怎么办?也就是说,我们想在某个位块的某个位置设置一个位为运行时提供的一个新的无关位的值?

我希望在英特尔 x86_64 上实现最快的实现(并忽略矢量化,我希望这与此无关)。另外,为简单起见,假设位块类型为uint32_t

编辑:我的答案是 C 而不是 C++,因为这个问题真的没有 C++ 风格。

【问题讨论】:

  • @fuz:不,否则这只是一个骗局。

标签: assembly optimization bit-manipulation bitwise-operators micro-optimization


【解决方案1】:

对于 32 位块类型,这里有两种可能的实现方式:

#include <cstdint>

uint32_t bit_assign_v1(uint32_t block, uint8_t bit_index, bool x)
{
    uint32_t mask = uint32_t { 1 } << bit_index;
    return (block & ~mask) | (((uint32_t) x) << bit_index);
}

uint32_t bit_assign_v2(uint32_t block, uint8_t bit_index, bool x)
{
    uint32_t mask = uint32_t { 1 } << bit_index;
    return x ? (block & ~mask) : (block | mask);
}

使用 GodBolt,我为这两个选项中的每一个都获得了不同优化的代码,随着我们更改平台和编译器,这些代码也会有所不同。这是 Skylake 的 example(或者更好的是,请查看 this version,它是相同的代码,但拆分为更多的 C 语句,以便您可以更好地将程序集与 C 代码相关联)。

GCC 8.2 汇编:

bit_assign_1:
        movzx   eax, sil
        btr     edi, eax
        movzx   edx, dl
        shlx    eax, edx, eax
        or      eax, edi
        ret
bit_assign_2:
        mov     ecx, 1
        shlx    esi, ecx, esi
        andn    eax, esi, edi
        or      esi, edi
        test    dl, dl
        cmove   eax, esi
        ret

clang 7.0 程序集:

bit_assign_1:                           # @bit_assign_1
        btr     edi, esi
        shlx    eax, edx, esi
        or      eax, edi
        ret
bit_assign_2:                           # @bit_assign_2
        mov     eax, edi
        btr     eax, esi
        bts     edi, esi
        test    edx, edx
        cmovne  edi, eax
        mov     eax, edi
        ret

我还没有对此进行任何基准测试。

【讨论】:

  • 这不是作业
  • 第一个 asm 在 Intel 上的吞吐量看起来更好:只有 5 uop,而在 Haswell 和更早的 cmov 为 2 时为 6)。但是在 Ryzen 上,btr 需要 2 微秒。 (不过,movzx eax, sil 是多余的。btr 掩盖了计数,因此btr edi, esi 将忽略低位。但希望在内联时消失。在第二个版本中,mov ecx,1 可以提升出循环,并且test dl,dl 可能会消失(由计算结果设置的标志)。
  • 我想知道你是否可以让 gcc 或 clang 发出 btrmov+btstest/cmov 来选择重置或设置版本?这些版本之间的选择可能取决于 3 个输入中的哪一个在延迟的关键路径上。第一个版本从bool x 准备好到结果准备就绪有 3c 延迟,但第二个版本只有 2c 延迟(使用单微指令 cmov。)如果 gcc 知道使用不同的注册movzx 以允许 mov 消除,但是:/ 但是如果 movzx 优化内联...
  • @Slava:嗯,这些函数返回赋值的结果,所以已经足够好了……
  • 是的,阅读链接的答案。在发出调用函数的代码时,gcc 和 clang 都满足了这种尚未标准化的行为,但只有 clang 在被调用者中依赖它。我忘记了ICC是否可以安全地调用clang代码,但是gcc和clang可以互相调用。手写 asm 调用 clang 代码也可能是个问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-04-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-08-28
  • 2011-07-18
相关资源
最近更新 更多