一行终端输入包含终止换行符是正常的。如果 RARS 不允许用户在没有换行符的情况下“提交”输入,您可以将最后一个字节归零。但是 RARS 读取字符串ecall
非常不方便地不返回长度,因此搜索 a\0
并不比只搜索 a 好\n
。
(Unixread
系统调用将返回一个长度:RARS 将其作为ecall
#63read
返回一个长度 in a0
,因此如果它允许标准输入的 fd=0,您可以使用它来读取输入。)
循环效率
每次循环迭代只做一个字节;您唯一要节省的是每次迭代 ( lb
) 的字节加载,代价是更多的 ALU 工作。
简单的方法看起来像这样,并且在大多数现实世界的 RISC-V 机器上可能更快。(特别是如果他们有任何缓存,这使得执行多个附近的加载而不是一个更广泛的加载变得便宜。)如果您真的关心优化,展开一些以隐藏加载延迟对于高性能有序机器可能是一个好主意这个循环用于潜在的大输入。(对于这个用例,你不应该这样做,因为它每个用户输入只运行一次,所以只要保持代码大小的紧凑。)
li t1, '\n'
.loop: # do{
lbu t0, (a0)
addi a0, a0, 1
bne t0, t1, loop # }while(*p != '\n')
# assume the string will *always* contain a newline,
# otherwise check for 0 as well
sb zero, -1(a0)
# a0 points to one-past-the-end of the terminating 0
# so if you want the string length, you can get it by subtracting
但是关于一次单词循环的设计选择还有更多要说的:
由于 RISC-V 具有字节存储指令,因此您无需屏蔽找到换行符的单词并将整个单词存储在sb x0, (position)
找到换行符的位置,即使您通过递增 a 找到该位置每个内循环移位计数的计数器(这也应该简化该循环)。
此外,如果您的缓冲区不是对齐单词的整数,那么存储整个单词尤其糟糕:您不想在缓冲区末尾执行非原子 RMW 字节。这对于线程安全来说是一个非常糟糕的习惯。(另请参阅 Erik 的回答:一般来说,一次一个单词的可能缺点,以及在 x86 和 x64 的同一页面内读取缓冲区末尾是否安全?)
(如果您要屏蔽一个单词并存储它,请使用not
而不是neg
/addi -1
来反转掩码中的位。 是withnot
的伪指令。通常,您可以向编译器询问类似的内容,例如 https:/ /godbolt.org/z/EPGYGosKd展示了 clang 如何为 RISC-V 实现。)xori
-1
x & ~mask
一次快速的单词
要真正一次快速地检查整个单词是否有换行字节,请word ^ 0x0a0a0a0a
将该字节值映射为 0,并将其他值映射为非零。 然后使用 bithack 来查找单词是否具有零字节https://graphics.stanford.edu/~seander/bithacks.html#ZeroInWord。(就像 glibc 的可移植 C 回退strlen
所做的那样:为什么 glibc 的 strlen 需要如此复杂才能快速运行?)。IIRC,这不是一个精确的测试(可能存在误报匹配),因此您需要快速检查整个单词,然后遍历字节一次检查一个以确保。如果没有,请返回单词循环。
当然,如果您有一些 SIMD 支持,使用 RV32 P(打包 SIMD)或 RV32 V(向量)扩展并行执行 4 或 8(或 16)字节比较,那就更好了。
如果您在未分配的缓冲区上执行此操作,您可能想要执行一次未对齐的加载(在检查它不会跨越页面或缓存行边界之后),然后到达对齐边界对齐的单词加载。或者循环一个字节直到一个字边界。(或 RV64 上的双字)。