我接到一个任务,要创建一个程序,该程序将读取文件的名称,然后将其内部复制到其他文件,该文件的名称也从输入中读取。我自己编写了程序,但它似乎什么也没做。
进一步实验,我发现,在读取第一个字符串时,程序还会在其中保存一个 '\n'
字符,这显然会导致搜索目标文件时出现一些问题。我想出了一个解决方案,但我并不完全喜欢,这就是为什么我来这里征求对代码和整体进一步改进的意见,也许?
我只固定了负责将文件名写入缓冲区的部分,直到出现 '\n'
。
.text
main:
#first block
sbrk(128)
mv s3, a0
li a7, 8
li a1, 127
ecall
for:
lw t0, 0(a0)
li s1, 0x000000ff
li s2, 0x0000000a
ff_and:
and t1, t0, s1
addi s4, s4, 1
beq t1, s2, kill
slli s1, s1, 8
slli s2, s2, 8
bnez s1, ff_and
addi a0, a0, 4
b for
kill:
neg s1, s1
addi s1, s1, -1
and t0, t0, s1
sw t0, 0(a0)
最佳答案
一行终端输入包含终止换行符是正常的。如果 RARS 不允许用户“提交”没有换行符的输入,您可以将最后一个字节清零。但是 RARS 读取字符串 ecall
非常不方便地不返回长度,因此搜索 \0
并不比仅仅搜索 \n
.
(Unix read
系统调用将返回一个长度:RARS 的 ecall
#63 read
会返回 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)
存储在找到换行符的位置,即使您通过为每个内循环移位计数递增计数器来找到该位置(这也应该简化该循环)。
此外,如果您的缓冲区不是全部对齐的单词,则存储整个单词尤其糟糕:您不想在缓冲区末尾之后执行非原子 RMW 字节。这对于线程安全来说是一个非常坏的习惯。 (另请参阅 Erik 的回答:一般情况下一次单词可能存在的缺点,以及 Is it safe to read past the end of a buffer within the same page on x86 and x64? )
(如果您要屏蔽一个单词并存储它,请使用 not
而不是 neg
/addi -1
来反转位在你的掩码中。not
是带有-1
的xori
的伪指令。一般来说,你可以向编译器询问类似的东西,例如 https://godbolt.org/z/EPGYGosKd 展示了 clang 如何为 RISC-V 实现 x & ~mask
。)
一次快速说出一个词
要真正快速一次检查整个单词的换行字节,请执行 word ^ 0x0a0a0a0a
将该字节值映射到 0 和其他值到非零。然后使用 bithack 来查找单词是否具有零字节 https://graphics.stanford.edu/~seander/bithacks.html#ZeroInWord 。 (就像 glibc 的可移植 C 后备 strlen
所做的那样: Why does glibc's strlen need to be so complicated to run quickly? )。 IIRC,这不是一个精确的测试(可能存在误报匹配),因此您需要快速检查整个单词,然后循环检查一个字节以确保确定。如果没有,则返回单词循环。
当然,如果您有一些 SIMD 支持,可以使用 RV32 P(打包 SIMD)或 RV32 V(矢量)扩展来并行执行 4 或 8(或 16)字节比较,那就更好了。
如果您在未分配的缓冲区上执行此操作,您可能需要执行一次未对齐加载(在 checking it's not going to cross a page or maybe cache-line boundary 之后),然后到达对齐字加载的对齐边界。或者一次循环一个字节,直到到达字边界。 (或 RV64 上的双字)。
关于string - 如何摆脱 RISC-V 中的 '\n'?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66886934/