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