在使用MSVC的内联汇编玩一点FPU时,我对释放FPU寄存器以提高性能感到有点困惑…
例如:
#include <stdio.h>
double fpu_add(register double x, register double y) {
double res = 0.0;
__asm {
fld x
fld y
fadd
fstp res
}
return res;
}
int main(void) {
double x = fpu_add(5.0, 2.0);
(void) printf("x = %fn", x);
return 0;
}
我什么时候必须在内联汇编中ffree
FPU寄存器?
在这个例子中,如果我决定将st(1)
寄存器改为ffree
,性能会更好吗?
fstp
也是下面指令的简写吗?
__asm {
fst res
ffree st(0)
}
注意:我知道FPU指令现在有点老了,但是处理它们作为SSE的另一种选择
ffree
指令允许您将x87的任何插槽标记为free
,而无需实际更改堆栈指针。因此,ffree st(0)
不弹出堆栈,只是将堆栈的顶部值标记为free/invalid,因此任何试图访问它的后续指令都会得到一个浮点异常。
要实际弹出堆栈,需要ffree st(0)
和fincstp
(增加指针)。或者更好的是,fstp st(0)
可以用一条便宜的指令完成这两件事。或者fstp st(1)
保留栈顶的值,丢弃旧的st(1)
。
但是使用其他指令的p
后缀版本通常更好、更容易(也更快)。在您的情况下,您可能需要
__asm {
fld x // push x on the stack
fld y // push y on the stack
faddp // pop a value and add it to the (now) tos
fstp res // pop and store tos
}
这将结束推入和弹出两个值,使fp堆栈保持与之前相同的状态。如果编译器正在生成x87的fp代码,那么在fp堆栈上留下一些东西可能会导致其他fp代码出现问题,因此应该避免。
或者更好的是,使用内存源fadd
来保存指令,如果您正在优化cpu,那么它不会更慢。(检查Agner Fog的微arch PDF和P5 Pentium及更新版本的指令表:似乎很好,至少收支平衡,并节省了更多的现代cpu,如Core2,可以进行内存源操作数的微融合。)
__asm {
fld x // push x on the stack
fadd y // ST0 += y
fstp res // pop and store tos
}
但是MSVC内联asm在包装单个指令(如fadd
)时固有地很慢,强制输入在内存中,即使编译器在asm语句之前在寄存器中可用。并且强制将结果存储在asm中,然后为return
语句重新加载,除非您使用诸如在st(0)
中留下值并在没有return
语句的情况下从函数末尾掉下来的hack。(MSVC实际上支持内联,但clang-cl/clang-fasm-blocks
不支持)
GNU C内联asm可以用适当的约束包装单个fadd
指令,以请求x87寄存器中的输入并告诉编译器输出在哪里(在st(0)
中),但是您仍然必须在fadd
和faddp
之间进行选择,而不是让编译器根据它是在寄存器中有值还是在内存中有值来选择。(https://stackoverflow.com/tags/inline-assembly/info)
编译器并不可怕,它们至少可以从普通的C源代码中生成这么好的代码。内联asm通常对性能没有帮助,除非您正在编写针对特定CPU进行仔细调优的整个循环,或者在编译器对某些事情做得很差的情况下。(查看编译器优化的asm输出,例如在https://godbolt.org/上)