assembly - 检查输入字符的最简洁方法是在汇编中介于 0~9 之间
问题描述
问题是在 RISC-V 中将字符串转换为 int
如果存在任何不是 0~9 的字符,则立即返回 -1
但我想知道是否有任何方法可以通过使用最少的指令来检查它
我的方法是将48和57(对应ASCII中的0~9)放在临时寄存器中,
并使用2个分支,首先检查<=57,然后检查>=48
但是它使用了太多的指令,并且需要额外的临时寄存器来存储 48 和 57。还有其他有效的方法吗?
解决方案
是的,因为无论如何你都必须减去'0'
,这样做然后无符号比较 c <= 9
或c < 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 在使用-O3
or编译时所选择的-O2
。使用gcc 将其反优化为底部为 a 并在中间中断-Os
的循环。(Godbolt 编译器资源管理器)。我不知道要尝试任何 RISC-V 架构或调整选项。j
btgu
-march=
因此,如果您想要代码大小和效率之间的良好平衡(尤其是对于 1 或 2 位数字的常见情况),您可能应该手动“编译”它。
GCC 用于(x<<3) + (x<<1)
乘以 10;clang 使用(并且在循环内部确实在和循环分支mul
之间共享一个常量。不幸的是,循环外部 clang 与, like比较,所以它需要两个常量。(RISC-V 有比较吗?IDK,TODO / 编辑欢迎是否这是否是一个错过的优化)。mul
bltu
9
9 < total
bge
<=
推荐阅读
- performance - 卡夫卡消费者延迟
- javascript - React.js 在 props 上使用 array.prototype.map 返回空数组
- pytorch - 拥抱脸 - RuntimeError:在 Azure Databricks 上的设备 0 上的副本 0 中捕获 RuntimeError
- swagger - Swagger 发送和接收数组
- java - 在链表操作中使用哨兵节点
- windows - 是否允许将路径作为 URL 传递给 Media Foundation?
- reactjs - 错误:元素类型无效,检查渲染方法
- c# - .NET Core 3.1 - HostingEnvironment 不包含 MapPath 的定义
- c++ - C++:如何使用大 int/double 克服内存问题?
- c# - 获取 Azure Key Valut 机密,返回 null/Not Found/Ojbect 未设置