我现在正在参加计算机体系结构课程,我们正在讨论基本的R-type和I-type指令(另外,这是一个 RISC 架构)等等。我可以似乎没有弄清楚如何优化这段代码。
说明:此代码在数组中添加单词(由$ s1指向),直到达到零。结果存储在$ t1中。 $ t0保留当前的单词。
add $t1, $zero, $zero # Initialize result to zero
again:
lw $t0, 0($s1) # Load the word from the array
beq $t0, $zero, done # Terminate if current word is a zero
add $t1, $t1, $t0 # Add current word to result
addi $s1, $s1, 4 # Point to the next word in the array
beq $t1, $t1, again # Loop again
done:
nop # Do nothing
我很难优化代码。我觉得beq $t1, $t1, again
(因为它总是如此)是不必要的,但我不知道如何删除它。这是我的尝试,但我现在意识到我的代码不会终止。
add $t1, $zero, $zero # Initialize result to zero
again:
lw $t0, 0($s1) # Load the word from the array
add $t1, $t1, $t0 # Add current word to result
addi $s1, $s1, 4 # Point to the next word in the array
bne $t1, $zero, again # If result is not zero, loop
done:
nop # Do nothing
我从不检查终止零点并跳转完成。但是,如果我添加另一个检查,那么代码是不是和以前一样?
答案 0 :(得分:2)
通常,您希望将顶部循环中的测试转换为底部循环中的测试。要做到这一点,你经常需要跳入(或多或少)循环体的中间进行第一次迭代。在伪代码中,你现在拥有的基本上是:
initialize sum
beginning:
load a word
if (done) goto end
add to sum
increment pointer
goto beginning
end:
为了优化它,我们希望将结构更改为:
initialize sum
goto start_loop
beginning:
add to sum
increment pointer
start_loop:
load a word
if (!done) goto beginning
这样每个循环只有一个跳转而不是两个跳转(它是一个短的向后跳转,所以目标几乎总是在缓存中)。
那就是说,我应该补充一点,这种优化实际上已经过时了 - 通过适当的分支预测,无条件跳转通常基本上是免费的。
编辑:由于已经提到了循环展开,我将加上我的两分钱。分支预测通常会使循环展开过时,除非您可以使用它来并行执行更多指令。这不是问题,但在现实生活中常常有用。例如,如果我们假设一个具有两个独立加法器的CPU,我们可以展开循环的两次迭代,并将这些迭代的结果添加到两个单独的目标寄存器中,因此我们通过在同一位添加两个值来利用这两个加法器时间。然后,当循环终止时,我们将这两个寄存器加在一起以获得最终值。在类似C的伪代码中,会出现类似这样的内容:
odds = 0
evens = 0
do {
evens += pointer[0];
odds += pointer[1];
pointer += 2;
while (pointer[0] && pointer[1]);
total = odds + evens;
如上所述,这增加了两个连续零的次要额外要求,以终止序列而不是一个,并且要添加的数组中至少有两个项目。但是请注意,并非真正的循环展开会带来主要优势,而是并行执行。
如果没有这一点,我们实际上只看到循环展开的好处,如果未采取的分支比采取的分支便宜(即使两者都被正确预测)。在较旧的处理器(例如,较旧的英特尔)上进行分支确实相对于未采用的分支进行了惩罚。同时,展开的循环将使用更多的缓存空间。因此,在现代处理器上,它经常是一个整体损失(除非,正如我之前所说,我们可以使用展开来获得并行性)。
答案 1 :(得分:1)
展开你的循环:
add $t1, $zero, $zero # Initialize result to zero
again:
lw $t0, 0($s1) # Load the word from the array
beq $t0, $zero, done # Terminate if current word is a zero
add $t1, $t1, $t0 # Add current word to result
addi $s1, $s1, 4 # Point to the next word in the array
lw $t0, 0($s1) # Load the word from the array
beq $t0, $zero, done # Terminate if current word is a zero
add $t1, $t1, $t0 # Add current word to result
addi $s1, $s1, 4 # Point to the next word in the array
lw $t0, 0($s1) # Load the word from the array
beq $t0, $zero, done # Terminate if current word is a zero
add $t1, $t1, $t0 # Add current word to result
addi $s1, $s1, 4 # Point to the next word in the array
lw $t0, 0($s1) # Load the word from the array
beq $t0, $zero, done # Terminate if current word is a zero
add $t1, $t1, $t0 # Add current word to result
addi $s1, $s1, 4 # Point to the next word in the array
# and so on to a reasonable amount, 4-8 times are common.
b again # Loop again
done:
nop