RISCV无分支编码

Bra*_*ram 7 assembly cpu-architecture riscv conditional-move branchless

在英特尔 AVX 上,存在无分支代码的可能性。您可以计算这两种情况,并根据条件混合结果,而不是针对 case0 或 case1 进行分支。

AVX 使用vblendps指令以 8 种方式实现浮动。

您还可以使用 x86 指令CMOVcc以标量方式(无需向量)执行此操作,该指令有条件地执行移动操作。

注意:ARM 有CSEL,NEON 有VBSL

RISCV64 可以做这样的标量移动吗,这样你就不必分支

a = c ? x : y;
Run Code Online (Sandbox Code Playgroud)

据我了解,RISCV 实现是有序的,因此在不需要分支时它比 x86 更有好处。(后者至少可以围绕一些指令进行洗牌,甚至可以推测性地分支以隐藏延迟。)

我能找到的最接近 riscv 的无分支操作是SLT(设置小于),但设置为 1 或 0,然后需要乘法?将 SLT 设置为 -1 或 0 不是更有用,这样我们就可以进行 AND 运算吗?

更新

做时:

int foo(int a, int b, int x, int y)
{
    return a < b ? x : y;
}
Run Code Online (Sandbox Code Playgroud)

我尝试了使用 SLT 的穷人版本的无分支。我不确定我是否完全正确,通过使用位掩码作为 0 - 条件(0|1),我想出了:

branchless:
    SLT t0,a0,a1
    SUB t0,zero,t0
    NOT t1,t0
    AND t0,a2,t0
    AND t1,a3,t1
    OR  a0,t0,t1
    RET
    .size   branchless, .-branchless
Run Code Online (Sandbox Code Playgroud)

作为无分支版本:

branched:
    BGE a0,a1,.L2
    MV  a3,a2
.L2:
    MV  a0,a3
    RET
    .size   branched, .-branched

Run Code Online (Sandbox Code Playgroud)

我想知道我是否为此使用了太多指令,但我测量随机数据上的分支版本比非分支版本稍快,但也不是快很多。

Pet*_*des 14

更新:请参阅 sh1对当前情况的回答:有一个条件零指令,例如cmovfrom x0cmov在扩展 B 进入 v1.0 之前, 完整内容已从计划的讨论中删除(并且扩展 B 被分成了一些单独的部分)。一篇文章提供了有关截至 2023 年中期情况的一些详细信息和链接。

当前的编译器也不再支持b单字母扩展名。


提议的 RISC-V 扩展 B 包括cmov(具有 4 个操作数:3 个输入和一个单独的目的地!)。(编写本答案的其余部分时,当前版本为 0.93。)

我认为 David Patterson(MIPS 和 RISC-V 背后的首席架构师之一)真的不喜欢cmov(以及像 SSE/AVX 这样的短向量 SIMD),并认为 CPU 应该专门处理“吊床”分支(向前跳过单个指令,例如如果他们想这样做的话。类似的事情。因此,这似乎是哲学纯粹性妨碍了包含有用说明的情况。(AArch64 是一种更加务实的设计,在对高性能实现至关重要的方面仍然是 RISC。)

和/或可能希望将指令限制为最多 2 个输入(如果没有任何其他 3 输入指令)。这意味着如果严格遵循此限制,标量管道仅需要 2 个寄存器读取端口,而不是 3 个。(这也意味着没有进位加法,当您必须处理同一加法运算的进位进位时,对于宽度超过 2 个寄存器的数字来说,扩展精度数学变得非常痛苦。)

可以像您所说的那样使用 AND/ANDnot/OR 的掩码进行模拟cmov,但这将需要相当多的指令,并且通常不值得,除非可能在宽而深的乱序机器上,其中由分支失误要大得多。(mask = (c == 0) - 1;您可以使用sltiu/add reg,reg, -1将 0 转换为 -1,将 1 转换为 0。)

尽管两种方式都有潜在的好处,但对于哪种微架构更能从 CMOV 中获益这一问题,您有点倒退了。有序机器已经必须在条件分支处等待条件解决,而无序机器处理控制依赖性与数据依赖性的方式非常不同。正如gcc 优化标志 -O3 使代码比 -O2 慢中所讨论的,数据依赖cmov可以创建循环携带的依赖链,这是高度可预测分支的更大瓶颈。

有一些乱序执行 RISC-V 设计,甚至可能有一些是开源的。例如,Erik Eidt 链接了伯克利无序机器 (BOOM)


扩展 B:他们把所有遗漏的有趣说明放在哪里

RISC-V 扩展 B 提案有一个条件移动,以及标量最小/最大、弹出计数、前导/尾随零计数、位域插入/提取、两个寄存器移位以及一堆更深奥的东西。 https:// Five-embeddev.com/riscv-bitmanip/draft/bext.html#conditional-move-cmov

看看建议的指令列表,令人惊讶的是基线 RISC-V 中遗漏的内容,例如窄整数的符号扩展(当前需要 slli/srai)(如果调用约定或加载指令尚未保证),并且标准大多数 ISA 都有诸如 popcount 和前导/尾随零计数之类的东西。

Godboltcmov使用、min和展示 clang 12.0sext.b。在那个 clang 版本中,-O3 -Wall -menable-experimental-extensions -march=rv32gcb0p93是做到这一点的魔法咒语。b0p93扩展 B 0.93 由字符串的一部分启用(扩展 B 尚未最终确定,我不知道 clang 14.0 正在寻找什么版本;它的错误消息没有帮助,而且只是简单地-march=rv32gcb没有让编译器实际使用cmov。)

//  -march=rv32gcb0p93 includes extension b 0.93 (0p93)

int sel(int x, int y, int c){
    return c ? x : y;
}
# extension B  clang
        cmov    a0, a2, a0, a1
        ret

# baseline gcc11.3  (clang and GCC12 waste several mv instructions)
        bne     a2,zero,.L2
        mv      a0,a1
.L2:
        ret
Run Code Online (Sandbox Code Playgroud)
int min(int x, int y, int c){
    return (x<y) ? x : y;
}
# extension B  clang
        min     a0, a0, a1
        ret

# baseline gcc
        ble     a0,a1,.L5
        mv      a0,a1
.L5:
        ret
Run Code Online (Sandbox Code Playgroud)
int sext(int c){
    return (signed char)c;
}
# extension B  clang
        sext.b  a0, a0
        ret

# baseline gcc
        slli    a0,a0,24
        srai    a0,a0,24
        ret
Run Code Online (Sandbox Code Playgroud)


sh1*_*sh1 8

好吧,cmov没成功。

现在您需要查看Zicond扩展来获取说明czero.eqzczero.nez. 它们返回第一个输入或零,具体取决于最后一个输入是否为零。

例如:

int cmov(bool c, int x, int y) {
    return c ? x : y;
}
Run Code Online (Sandbox Code Playgroud)

给出:

cmov(bool, int, int):                             # @cmov(bool, int, int)
        czero.nez       a2, a2, a0
        czero.eqz       a0, a1, a0
        or      a0, a0, a2
        ret
Run Code Online (Sandbox Code Playgroud)

显然,当其中一个操作数恒定为零时,这看起来要好得多,这很常见,或者如果您正在寻找类似的东西,c ? x : (x + y)那么它会变成x + (c ? 0 : y).

要立即在 clang 中启用此优化需要:-menable-experimental-extensions -march=rv64gc_zicond1p0

一旦一切都解决了,我想这将变成:-march=rv64gc_zicond

如果您已经设置了-march=,只需_zicond1p0在其末尾添加 或 即可。

SIMD空间 ( -march=rv64gcv) 中,您有__riscv_vmerge_*()内在函数。

B 扩展家族中幸存下来min/max。您可以使用 访问这些内容-march=rv64gc_zbb,除了明显的用途之外,您有时还可以重构内容以将它们用作屏蔽操作。

  • 这个答案也值得大家点赞!自从这篇文章发布以来,三个人对我的答案投了赞成票(希望是因为条件移动的计算机架构背景),但我的答案仍然是唯一一个真正回答了当前 RISC-V 问题的人! (2认同)