首页 > 解决方案 > 检查输入字符的最简洁方法是在汇编中介于 0~9 之间

问题描述

问题是在 RISC-V 中将字符串转换为 int

如果存在任何不是 0~9 的字符,则立即返回 -1

但我想知道是否有任何方法可以通过使用最少的指令来检查它

我的方法是将48和57(​​对应ASCII中的0~9)放在临时寄存器中,
并使用2个分支,首先检查<=57,然后检查>=48

但是它使用了太多的指令,并且需要额外的临时寄存器来存储 48 和 57。还有其他有效的方法吗?

标签: assemblydigitsmicro-optimizationriscvatoi

解决方案


是的,因为无论如何你都必须减去'0',这样做然后无符号比较 c <= 9c < 10。请参阅^= 32 背后的想法是什么,它将小写字母转换为大写字母,反之亦然?对于范围检查技巧。

我们可以在 C 中执行此操作,然后看看它是如何编译的,作为紧凑型 RISC-V 实现的起点。这个 C 的结构类似于NASM 汇编中的 asm 将输入转换为整数?,希望 GCC 或 clang 将使用类似的循环结构。如果您手动翻译它,您可能需要这种循环结构,或者对其进行调整,以便在有序 RISC-V 上实现更好的软件流水线,尤其是隐藏加载使用延迟。这种循环结构在现代 x86 上非常棒,其中 OoO 推测执行隐藏了分支和加载使用延迟。

// C intentionally written exactly like hand-written asm
// Translate this to asm by hand, including the loop structure.
// or compile it if you want more bloated asm.

unsigned str_to_uint(const unsigned char *p) {
    unsigned dig = *p - '0';
    unsigned total = dig;  // peel first iter, optimize away the  + 0 * 10
    if (total < 10)        // <10 can share a constant with *10
        goto loop_entry;
    else // fall through to the uncommon case of no valid digits
        return 0;

    do {
        total = total*10 + dig;
     loop_entry:            // branch target = loop entry point
        dig = *++p - '0';
    } while(dig < 10);

    return total;
}

我在第一次迭代中跳过了total * 10 + dig使用分支,所以我们不妨将它作为我们进入循环的入口,以最大限度地减少总代码量。

另一种选择是将另一个循环迭代剥离到循环的顶部。这是 GCC 和 clang 在使用-O3or编译时所选择的-O2。使用gcc 将其反优化为底部为 a 并在中间中断-Os的循环。(Godbolt 编译器资源管理器)。我不知道要尝试任何 RISC-V 架构或调整选项。jbtgu-march=

因此,如果您想要代码大小和效率之间的良好平衡(尤其是对于 1 或 2 位数字的常见情况),您可能应该手动“编译”它。

GCC 用于(x<<3) + (x<<1)乘以 10;clang 使用(并且在循环内部确实在和循环分支mul之间共享一个常量。不幸的是,循环外部 clang 与, like比较,所以它需要两个常量。(RISC-V 有比较吗?IDK,TODO / 编辑欢迎是否这是否是一个错过的优化)。mulbltu99 < totalbge <=


推荐阅读