问题是:
给定一个非零整数序列,后跟0,找到序列中最大的整数并将结果放入x5
。使用DD
汇编程序命令将初始测试序列-1、55,-3、7、0存储在内存的开头。
我已经尝试了多种变体:
src: DD -1, 5, -3, 7, 0
add x6, x0, x0
loop: ld x5, src(x6)
sd x7, dst(x6)
beq x5, x0, end
bge x5, x7, skip
skip: addi x6, x6, 8
beq x0, x0, loop
end: ebreak x0, x0, 0
dst: DM 1
但是,没有任何效果,我需要一些有关如何回答此问题以及RISC-V如何工作的帮助。
我认为您对寄存器的预期用途是:
x5
包含从序列读取的当前值。x6
包含序列的索引。x7
包含到目前为止所看到的最大值。x7
由于最大值随时都是单个值,因此无需存储在内存中。寄存器x7
应在开始时进行初始化。
x7
可以使用它可以保持的最低值进行初始化,即-2 63:
addi x7, x0, 1
slli x7, x7, 63
从序列中读取的任何值(可能的最小值除外)都会导致当前最大值被更新。
另外,您可以直接将序列的第一个元素加载到其中,x7
因为总是有一个元素可加载(0
如果为空序列则终止):
ld x7, sr(x6)
您的代码中的以下分支指令:
bge x5, x7, skip
skip: addi x6, x6, 8
无论条件是否成立(x5
> = x7
),执行的下一条指令始终为addi x6, x6, 8
。在skip
标签之后,这两个指令之间缺少的是用于更新到目前为止所看到的当前最大值的代码,即,用于将内容从x5
移至的指令x7
。操作数x5
和x7
的的bge
指令也必须换,因为你想跳过的代码更新最大的时候x7
> = x5
成立(即当没有更新最大最大的已经是大于或等于当前值):
bge x7, x5, skip # skip the update of the maximum?
addi x7, x5, 0 # update new maximum value
skip: addi x6, x6, 8
因此,如果分支条件确实保持,即,如果x7
(最大值)是大于或等于至x5
(当前值读出的),用于更新所述最大的代码被跳过。
而不是在循环中有两个分支指令:beq x5, x0, end
如果在序列中已达到零值,则该指令将终止循环,并且无条件跳转beq x0, x0, loop
作为循环的最后一条指令(用于重复循环),您可以重新排列代码,以便循环的最后一条指令是:
bne x5, x0, loop # is the end of the sequence not reached yet?
这将同时替换beq x5, x0, end
和beq x0, x0, loop
:如果已达到序列的终止值(即零),则终止该序列,否则,将再次循环。
牢记所有这些内容,您的代码可能如下所示:
src: DD -1, 5, -3, 7, 0
add x6, x0, x0 # initialize the index
ld x7, sr(x6) # initialize the maximum
addi x5, x7, 0 # initialize with the first value
beq x5, x0, end # is the sequence empty?
loop: bge x7, x5, skip # skip the update of the maximum?
addi x7, x5, 0 # update the maximum with the new value read
skip: addi x6, x6, 8 # update the index
ld x5, sr(x6) # load the next value from the sequence
bne x5, x0, loop # is the end of the sequence not reached yet?
end:
addi x5, x7, 0 # place the final result in x5 (your problem's assignment)
ebreak x0, x0, 0
该数组是隐式长度的,因此加载第一个元素始终是安全的。您可以使用它而不是使用2个regs构造INT64_MIN。如果终止符之前没有任何元素,则返回值毫无意义。这对于剥离第一个迭代也可能是一个很好的设置,因此您可以将loop-exit条件分支放在它所属的循环的底部。
我认为您引入了一个错误。现在
x5
,在检查其是否为终止符之前,先检查其是否为新的最大值。我将ld + bne放在底部附近,跳到那里进入循环。或剥离第一次迭代的一部分,以便您可以陷入循环。(即检查循环主体的主要部分是否需要运行0次,如果是这种情况,请跳过该循环。像正常情况一样,可能需要运行0次的循环。)是的,这就是我在评论的最后一部分中所描述的:跳转以跳过整个循环,然后再进入普通的do {} while循环结构。现在,您还避免将第一个元素加载两次。
感谢您的帮助,它确实为我清除了一切
顺便说一句,为了提高性能,您可能需要编写循环,因此正常情况(无新的最大值)是一个未采用的分支。即使正确预测,采用分支也比没有采用具有更多的成本,尤其是在超标量CPU上。因此,例如,您可以有条件地跳到另一个块(在函数结束之后),该块将更新
x7
并跳回。可能会复制循环尾部,以便您可以尝试(跳转到end
,或返回的整个函数的重复结尾)。或者,loop
如果我们没有加载终止符,则在为下一次迭代设置后直接跳转到。