【问题标题】:Cleanest way to check input char is between 0~9 in Assembly检查输入字符的最简洁方法是在汇编中介于 0~9 之间
【发布时间】:2021-02-03 23:28:24
【问题描述】:

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

如果存在非0~9的字符,立即返回-1

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

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

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

【问题讨论】:

  • 是的,因为无论如何你都必须减去'0',这样做然后无符号比较c <= 9c < 10。有关范围检查技巧,请参阅 What is the idea behind ^= 32, that converts lowercase letters to upper and vice versa?NASM Assembly convert input to integer? 是使用该想法的循环的 x86 asm 实现。它应该很好地转化为 RISC-V;尝试用 C 语言编写它并使用编译器。
  • 我很好奇我可以让 GCC / clang 为 C 版本发出什么;结果不如我手写的 x86 asm 好,所以我写了一个答案。看起来也错过了 RISC-V 版本的一些优化。顺便说一句,gcc/clang 知道我提到的范围检查技巧;您通常不需要手动实现它。但是在这里将它与-= '0' 结合起来转换为整数是很有用的。

标签: assembly digits micro-optimization riscv atoi


【解决方案1】:

是的,因为无论如何您都必须减去 '0',请执行此操作,然后进行无符号比较 c <= 9c < 10。有关范围检查技巧,请参阅 What is the idea behind ^= 32, that converts lowercase letters to upper and vice versa?

我们可以在 C 中执行此操作,然后看看它是如何编译的,作为紧凑型 RISC-V 实现的起点。这个 C 的结构类似于 NASM Assembly convert input to integer? 中的 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 在使用 -O3-O2 编译时选择的。使用-Os gcc 将其反优化为一个循环,底部有一个j,中间有一个btgu 中断。 (Godbolt compiler explorer)。我不知道有任何 -march= RISC-V 架构或调整选项可以尝试。

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

GCC 使用(x&lt;&lt;3) + (x&lt;&lt;1) 乘以 10; clang 使用mul(并且在循环内部确实在mulbltu 循环分支之间共享一个常量。不幸的是,在循环外部clang 与9 进行比较,例如9 &lt; total,因此它需要两个常量。(是否RISC-V 有一个bge&lt;= 比较?IDK、TODO / 欢迎编辑这是否是一个错过的优化)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-04-04
    • 2011-06-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-07-27
    • 2017-05-25
    相关资源
    最近更新 更多