我对Comp Sci类有一个简单的问题,我的任务是将函数转换为MIPS汇编语言。我相信我有一个正确的答案,但我想验证它。
这是C函数
int strlen(char *s) {
int len;
len=0;
while(*s != '\0') {
len++;
s++;
}
return len;
}
谢谢!
strlen:
add $v0, $zero, $zero
loop:
lbu $t0, 0($a0)
addi $a0, $a0, 1
addi $v0, $v0, 1
bne $t0, $zero, loop
s_end:
addi $v0, $v0, -1
j $ra
答案 0 :(得分:2)
我认为在s = 0的情况下,while循环是不正确的。 它应该是这样的:
...
lbu $t0, 0($a0)
loop:
beq $t0, $zero, s_end # *
...
b loop
s_end:
...
*您可以使用宏指令(beqz $ t0,s_end)而不是beq指令。
答案 1 :(得分:1)
是的,你有一个正确的asm版本,我喜欢你在测试t0的值之前做了尽可能多的工作,以便尽可能多地从内存加载。
答案 2 :(得分:0)
是的,对我来说看起来正确,而且效率很高。使用结构像while
的asm实现do{}while()
循环是在asm中循环的标准和最佳方法。 Why are loops always compiled into "do...while" style (tail jump)?
更直接的C音译会在递增*s
之前检查len
。
例如通过剥离第一个迭代并将其转换为可以跳过整个循环的空字符串的加载/分支。 (并且对循环主体进行重新排序,这可能会使负载接近分支,由于负载延迟而使性能变差。)
您可以在循环后优化len--
的过冲校正:从len=-1
而不是0
开始。使用li $v0, -1
仍然可以通过一条指令来实现:
addiu $v0, $zero, -1
进一步的优化步骤是仅使指针在循环内递增,并用len = end - start
查找末尾的长度。
我们可以通过将传入的指针复制到另一个reg时偏移传入的指针来校正不符一的情况(不计算终止符)。
# char *s input in $a0, size_t length returned in $v0
strlen:
addiu $v0, $a0, 1 # char *start_1 = start + 1
loop: # do{
lbu $t0, ($a0) # char tmp0 = load *s
addiu $a0, $a0, 1 # s++
bne $t0, $zero, loop # }while(tmp0 != '\0')
s_end:
subu $v0, $a0, $v0 # size_t len = s - start
jr $ra
我使用了addiu
/ subu
,因为我不希望它在指针的签名溢出时出错。您的版本可能还应该使用addiu
,因此它适用于最大4GB的字符串,而不仅仅是2GB。
未经测试,但我们可以通过正确性进行思考:
s
指向0):到达最终减法后,我们有v0=s+1
(从循环之前开始)和a0=s+1
(从第一个循环开始) / only迭代失败,因为它加载了$t0 = 0
)。减去这些得到len=0
= strlen("")
len = (s+2) - (s+1) = 1
。对于具有分支延迟插槽的MIPS,可以分别在bne和jr之后对addiu和subu进行重新排序,以填充这些分支延迟插槽。 (但是bne
紧接在加载之后,因此经典的MIPS将不得不停顿,甚至在MIPS I上用nop填充加载延迟插槽,而不能互锁负载。)
当然,如果您实际上关心的是中小型字符串(不仅是很小的字符串)(例如8或16字节以上)的实际strlen
性能,请使用bithack一次检查整个单词是否具有0
个字节。
Why does glibc's strlen need to be so complicated to run quickly?