MSVC内联汇编FPU寄存器释放时机、性能影响及fstp等效问题
问题背景
在使用MSVC内联汇编开展x87 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 = %f\n", x); return 0; }
待解答技术问题如下:
- MSVC内联汇编场景下,需要在什么时机使用
ffree指令释放FPU寄存器? - 上述示例代码中,如果主动使用
ffree指令释放st(1)寄存器,是否能够获得更好的运行性能? fstp指令是否为如下指令序列的简写形式:
__asm { fst res ffree st(0) }
备注:提问者知晓x87 FPU指令在当下已不算主流新技术,仅将其作为可与SSE配合使用的备选技术方案开展研究。
问题解答
关于
ffree的使用时机:
x87 FPU是深度为8层的环形寄存器栈,常规浮点运算中使用带p后缀的栈操作指令(比如faddp、fstp这类自动弹栈的运算/存储指令)时,硬件会自动完成值计算、栈顶指针移动、闲置寄存器标记为空的全流程,这种场景完全不需要手动调用ffree。
只有两类场景需要手动用ffree:一是你直接写入了某个非栈顶的st(n)寄存器,后续不会走正常出栈流程消费这个值,在内联汇编块退出前必须手动ffree掉这些闲置寄存器——MSVC对内联汇编有明确约定:退出__asm块时,除了浮点返回值允许放在st(0),其余FPU寄存器栈必须为空,否则会直接破坏编译器后续生成代码的FPU运行状态;二是做FPU异常处理、手动重置FPU栈状态时,用ffree批量标记无效寄存器为空,恢复栈可用状态。关于示例中手动
ffree st(1)的性能影响:
不仅不会提升性能,还会直接导致程序出错。先拆解示例的指令执行流:fld x压栈后st(0)=x,fld y压栈后st(0)=y、st(1)=x;无操作数的fadd默认取st(0)和st(1)相加,结果写入st(1)后自动弹栈,等fadd执行完,栈里只剩st(0)存着加法结果,根本不存在有效的st(1)寄存器。这时候执行ffree st(1)会把栈里其他位置的有效数据标记为空,轻则计算结果错误,重则触发FPU栈溢出异常。而且ffree本身有固定的指令执行开销,无意义的额外指令只会拖慢性能。关于
fstp和fst + ffree st(0)的等价性:
两者完全不等价。fstp的完整行为是:先把st(0)的值写入目标操作数,然后调整FPU状态字里的栈顶指针(TOP字段),将原栈顶位置标记为空,完成真正的出栈操作。而fst + ffree st(0)只做了值存储、标记st(0)为空两步,不会调整栈顶指针,后续执行压栈等操作时会直接在栈上留下无效空洞,几次运算后就会出现FPU栈状态混乱,和fstp的逻辑行为有本质区别。
内容的提问来源于stack exchange,提问作者Rabios

