var _gaq = _gaq || []; _gaq.push(['_setAccount', 'UA-333696-1']); _gaq.push(['_trackPageview']); _gaq.push(['_trackPageLoadTime']); (function() { var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true; ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); })();
  • 2008年03月16日

    【翻译】FPO

    分类:

    原作者:Larry Osterman

    原文链接

     

    上周我与一个做性能的家伙聊天,他提到的内容让我很惊叹。很显然他遇到的性能问题看上去与第三方驱动程序相关。不幸的是,他的问题在于弄要清楚是怎么回事错的,因为该供应商使用FPO编写驱动程序(并未提供符号),所以性能家伙不能追踪问题的根源。

     

    我惊讶的原因是我没有意识到还有人在用FPO

     

    什么是FPO

    要知道答案,你必须尽快返回到史前。

    Intel8088处理器有一组非常有限的寄存器(我正忽略段寄存器),它们是:

    AX

    BX

    CX

    DX

    IP

    SI

    DI

    BP

    SP

    FLAGS 窗体底端

     

    使用这样的一组受限寄存器,这些寄存器都被分配用作特定目的。AX, BX, CXDX是“通用”寄存器,SIDI是“索引”寄存器,SP是“栈指针”,BP是“帧指针”,IP是“指令指针”,还有FLAGS是一个只读寄存器包括若干位来指示处理器当前的状态(例如,是否先前的算术或逻辑指令的结果是0)。

     

    BX, SI, DIBP寄存器是特别的因为它们可被用作“索引”寄存器,索引寄存器对一个编译器极为重要,因为它们被用于通过一个指针访问内存。换句话说,如果你有一个结构它位于内存中偏移0x1234,你可以将一个索引寄存器设置为值0x1234并访问相对于该位置的值。例如:

    MOV    BX, [Structure]

    MOV    AX, [BX]+4

    BX寄存器设置为指向[Structure]内存的值并将AX的值设置成WORD位于相对该结构开始的第四个字节。

     

    有一点要注意的是SP寄存器不是一个索引寄存器。这意味着若要访问栈上的变量,你需要使用一个不同的寄存器,这就是BP寄存器的来源-BP寄存器专门用于访问栈上的值。

     

    386出来后,它们扩充不同的寄存器到32位,而且它们修复了限制,即仅BX, SI, DIBP能被用作索引寄存器。

    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二进制文件,但被关掉对VistaWindows二进制文件(译注:其实自XP SP2起就已没有FPO),因为它不再必须-自1995年以来机器变得足够快以致靠FPO达到的性能提升不足够反击FPO导致的在调试和分析中的痛苦。

    分享到:

    评论

  • 解答了我之前的一些疑惑,多谢!