首页 > 解决方案 > 哪一组指令性能更好?

问题描述

我有一个教授的作业,其中一部分引发了关于无分支编程的对话。目标是使用该指令将此 C 代码转换为 MIPS 程序集(假设ab分别位于寄存器$s0和中) 。$s1slt

if (a <= b)
  a = 0;
else
  b = 0;

预期的反应是:

        slt $t0,$s1,$s0    ; Set on less-than (if $s1 < $s0 then set $t0 to 1, else 0).
        bne $t0,$0,else    ; Branch on not-equal (jump to `else` if $t0 != $0)
        add $s0,$0,$0      ; $s0 = $0 + $0, aka $s0 = $0 * 2
        j   done           ; Unconditional jump to `done`
else:   sub $s1,$0,$0      ; $s1 = $0 - $0, aka `$s1 = 0`
done: 

但是,我提交了以下无分支解决方案(据我了解,无分支编程在性能方面是首选):

        slt  $t0,$s1,$s0    ; Set on less-than (if $s1 < $s0 then set $t0 to 1, else 0).
        mul  $s0,$s0,$t0    ; $s0 = $s0 * $t0 (truncated to 32 bits)
        xori $t0,$t0,0x1    ; $t0 = XOR( $t0, 1 )
        mul  $s1,$s1,$t0    ; $s1 = $s1 * $t0 (truncated to 32 bits)

我知道这是一个小案例,其中任何性能提升都可以忽略不计,但我想知道我使用的思路是否正确。

我的教授指出,mul指令很复杂(阅读:硬件密集型),因此(如果在硬件中实现)使用较少指令所获得的任何性能提升最终都会由于这种复杂性而丧失。这是真的?

标签: performancemips

解决方案


不可能这么说,因为您没有指定 MIPS 实现,这些年来已经有很多了。

然而,乘法可能更昂贵,尤其是在早期的 MIPS 处理器上,每个处理器可能花费两个(或三个)周期。

无条件控制流的另一种方法是从 中减去一个slt并使用and而不是mul,然后xor使用 -1。这将在版本上花费一条额外的指令mul,因此不清楚哪个会更快,但确实涉及“更简单”的指令。

    slt  $t0,$s1,$s0    ; Set on less-than (if $s1 < $s0 then set $t0 to 1, else 0).
    subi  $t0, $t0, 1   ; 1/true->0, 0/false->-1
    and  $s1,$s1,$t0    ; $s1 &= $t0-1        mask is either 0 or -1
    xori $t0,$t0,-1     ; $t0 = ~ $t0
    and  $s0,$s0,$t0    ; $s1 &= ~ (t0-1)     mask is either -1 or 0

这是否比条件逻辑快取决于处理器和数据集,假设这是在一个循环中(如果不是在某个级别的循环中,那么这个问题就不那么重要了)。

数据集将决定分支预测是否有效。每次分支预测错误时,都会花费几个周期(当然取决于处理器的实现)。如果分支预测 100% 有效,那么条件逻辑可能是最好的。但是如果数据集与分支预测不符,那么每次执行可能会产生几个周期的开销,因此无条件逻辑可能是最好的。

如果处理器有一些额外的操作码,这样就不需要减去 1 了,然后还有 else 部分的反转,那就太好了。在这种假设情况下,我们将有:

slt $t0, $s1, $s0
sameIfTrueOrZero  $s0, $s0, $t0   # $s0 = ($t0 ? $s0 : 0)
sameIfFalseOrZero $s1, $s1, $t0   # $s1 = ($t0 ? 0 : $s1)

这些将是硬件实现的简单指令——它们不会对 ALU 或指令和操作数编码征税。


一些更高级的处理器可能能够在上述某些方面融合操作或双重问题,从而降低执行成本。


推荐阅读