-
2008年03月16日
【翻译】FPO
分类:原作者:Larry Osterman
上周我与一个做性能的家伙聊天,他提到的内容让我很惊叹。很显然他遇到的性能问题看上去与第三方驱动程序相关。不幸的是,他的问题在于弄要清楚是怎么回事错的,因为该供应商使用FPO编写驱动程序(并未提供符号),所以性能家伙不能追踪问题的根源。
我惊讶的原因是我没有意识到还有人在用FPO。
什么是FPO?
要知道答案,你必须尽快返回到史前。
Intel的8088处理器有一组非常有限的寄存器(我正忽略段寄存器),它们是:
AX
BX
CX
DX
IP
SI
DI
BP
SP
FLAGS
使用这样的一组受限寄存器,这些寄存器都被分配用作特定目的。AX, BX, CX和DX是“通用”寄存器,SI和DI是“索引”寄存器,SP是“栈指针”,BP是“帧指针”,IP是“指令指针”,还有FLAGS是一个只读寄存器包括若干位来指示处理器当前的状态(例如,是否先前的算术或逻辑指令的结果是0)。
BX, SI, DI和BP寄存器是特别的因为它们可被用作“索引”寄存器,索引寄存器对一个编译器极为重要,因为它们被用于通过一个指针访问内存。换句话说,如果你有一个结构它位于内存中偏移0x1234,你可以将一个索引寄存器设置为值0x1234并访问相对于该位置的值。例如:
MOV BX, [Structure]
MOV AX, [BX]+4
将BX寄存器设置为指向[Structure]内存的值并将AX的值设置成WORD位于相对该结构开始的第四个字节。
有一点要注意的是SP寄存器不是一个索引寄存器。这意味着若要访问栈上的变量,你需要使用一个不同的寄存器,这就是BP寄存器的来源-BP寄存器专门用于访问栈上的值。
当386出来后,它们扩充不同的寄存器到32位,而且它们修复了限制,即仅BX, SI, DI和BP能被用作索引寄存器。
EAX
EBX
ECX
EDX
EIP
ESI
EDI
EBP
ESP
FLAGS
这是一件好事,所有的突然,而不是被约束至3个索引寄存器,编译器可以使用它们中的6个。
由于索引寄存器是用于访问结构,对一个编译器它们就像金子-更多的它们是一件好事,并且值得几乎任何代价来获得多个它们。
某些异常聪明的人意识到既然ESP现在成了索引寄存器,EBP寄存器不再致力于访问栈上的变量。换句话说,代替:
MyFunction:
PUSH EBP
MOV EBP, ESP
SUB ESP, <LocalVariableStorage>
MOV EAX, [EBP+8]
:
:
MOV ESP, EBP
POP EBP
RETD
若要访问栈上的第一个参数(EBP 0是旧的EBP值,EBP 4是返回地址),你可以改为执行:
MyFunction:
SUB SP, <LocalVariableStorage>
MOV EAX, [ESP+4+<LocalVariableStorage>]
:
:
ADD SP, <LocalVariableStorage>
RETD
这工作的很好-所有的突然,EBP可被重用和用作另一个通用寄存器!编译器人员称这种优化“帧指针省略”(Frame Pointer Omission),并进而缩写为FPO。
但对FPO来说有一个小问题。
如果你查看FPO之前的示例MyFunction,你会注意到第一个指令按照例行是PUSH EBP后跟着一个MOV EBP, ESP。那有一个有趣和非常有用的负作用。它实质上创建了一个单链表以链接为每个函数调用的帧指针。从EBP的一个例程,你可以恢复一个函数的整个调用栈。这对调试器来说是难以置信地有用-它意味着调用栈完全可靠,即使你没有正被调试的所有模块的符号。不幸的是,当FPO被启用时,该栈帧表已丢失-信息根本没被跟踪。
要解决这问题,编译器家伙放置当FPO被启用时已丢失的信息到PDB文件作为二进制文件。所以,当你有模块的符号,你可以恢复所有栈信息。
FPO已被启用对NT 3.51中所有Windows二进制文件,但被关掉对Vista中Windows二进制文件(译注:其实自XP SP2起就已没有FPO),因为它不再必须-自1995年以来机器变得足够快以致靠FPO达到的性能提升不足够反击FPO导致的在调试和分析中的痛苦。
随机文章:
网页应用程序反应时间 2009年03月03日【翻译】如何测试AJAX应用程序(2) 2008年08月22日【翻译】如何测试AJAX应用程序(1) 2008年06月15日【翻译】可测性合约成为异常的邻居 2008年05月04日【翻译】编程基础- 7 –回到基础知识:内存 2008年04月28日
收藏到:Del.icio.us

评论