C语言 MSVC内联汇编:释放FPU寄存器以提高性能



在使用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;
}

我什么时候必须在内联汇编中ffreeFPU寄存器?

在这个例子中,如果我决定将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)中),但是您仍然必须在faddfaddp之间进行选择,而不是让编译器根据它是在寄存器中有值还是在内存中有值来选择。(https://stackoverflow.com/tags/inline-assembly/info)

编译器并不可怕,它们至少可以从普通的C源代码中生成这么好的代码。内联asm通常对性能没有帮助,除非您正在编写针对特定CPU进行仔细调优的整个循环,或者在编译器对某些事情做得很差的情况下。(查看编译器优化的asm输出,例如在https://godbolt.org/上)

最新更新