我正在考虑如何取消mips32中的带符号整数。我的直觉是使用2的补码的定义,例如:(假设$s0
是要取反的数字)
nor $t0, $s0, $s0 ; 1's complement
addiu $t0, $t0, 1 ; 2's = 1's + 1
然后我意识到可以做到这一点:
sub $t0, $zero, $s0
所以...有什么区别?哪个更快? IIRC子程序将尝试检测溢出,但这会使速度变慢吗?最后,还有其他方法吗?
答案 0 :(得分:3)
subu $t0, $zero, $s0
是最好的方法,也是编译器的作用。
在任何给定的MIPS实现上,大多数简单的ALU指令(加/减/和/减)具有相同的性能。用一条简单的指令而不是两条简单的指令完成相同的工作是代码大小,延迟和吞吐量的胜利。
更少的指令并不总是更好,但是作为经典的RISC ISA的MIPS除了mult / div / rem之外没有很多“慢”指令。
sub
而不是subu
会在-INT_MIN
上引发异常,您可以避免在nor / add版本中使用addiu
。除非特别地想要签名溢出引发异常,否则您应该始终使用u
和sub
指令的add
版本。 C编译器始终使用u
版本。 (在C语言中,带符号的溢出是未定义的行为。这意味着它允许出错,但不是必需的出错,通常没有人希望这样做。编译器希望能够优化和引入创建临时值的转换C抽象机中从未存在过,因此这样做必须避免出错。)
On the Godbolt compiler explorer, MIPS gcc5.4 -O3编译
int neg(int x) { return -x; }
进入
neg(int):
j $31
subu $2,$0,$4 # in the branch delay slot
完全符合我们的预期。询问编译器通常是找到在asm中执行操作的有效方法的好方法。
IIRC子程序将尝试检测溢出,但这会使速度变慢吗?
不。据我所知,在无例外情况下,sub
的性能与subu
相同。
CPU针对常见情况进行了优化。接受异常的情况在普通代码中很少发生,以至于例外需要花费很多时间。因此,CPU内核只需在将任何不良结果写回到寄存器文件或存储到高速缓存/内存中之前检测到异常。在任何MIPS管道上,执行和回写之间至少有几个管道阶段。
在有符号溢出的情况下,ALU可以在与结果相同的周期内产生溢出信号。 (具有大多数指令更新的带有“标志”寄存器的ISA一直在执行add
指令的正常操作中执行此操作:如果软件希望对x86或ARM上的签名溢出进行特殊处理, d在溢出标志上使用条件分支(在x86上为OF,在ARM上为V)。MIPS的特殊之处在于,除了对有符号的溢出采取例外处理之外,很难做其他任何事情。