【问题标题】:Why is it good to avoid instruction branching where possible?为什么在可能的情况下避免指令分支是好的?
【发布时间】:2011-08-05 10:58:59
【问题描述】:

我经常读到,从性能的角度来看,分支(在汇编指令级别)是不好的。但我还没有真正明白为什么会这样。那么,为什么?

【问题讨论】:

    标签: programming-languages assembly system cpu


    【解决方案1】:

    大多数现代处理器 prefetch 指令甚至 speculatively execute 它们在代码流到达该指令之前。有一个分支意味着突然有两条不同的指令可能是下一条指令。至少有三种可能的方式可以与预取交互:

    • 未预取分支后的指令。 instruction pipeline 变为空,处理器必须等待,因为下一条指令在最后一刻被提取,从而降低了性能。
    • 处理器可以猜测将采用哪个分支 (branch prediction) 并预取并执行适当的指令。如果它猜到了错误的分支,它将不得不放弃已完成的工作,并等待获取正确的指令。
    • 处理器可以获取并执行两个分支,然后丢弃未采用的分支的结果。

    根据处理器和特定代码,与没有分支的等效代码相比,分支可能会或可能不会产生显着的性能影响。如果执行代码的处理器使用分支预测(大多数都这样做)并且大多数情况下正确猜测特定代码段,则可能不会对性能造成重大影响。另一方面,如果它大部分都猜错了,它可能会大大减慢速度。

    对于一段特定的代码,很难预测删除分支是否会显着加快代码速度。在进行微优化时,最好衡量两种方法的性能,而不是猜测。

    【讨论】:

      【解决方案2】:

      这很糟糕,因为它干扰了instruction prefetch。现代处理器可以开始加载下一个命令的字节,同时仍处理第一个命令以更快地运行。当发生分支时,必须丢弃预取的“下一个命令”,这会浪费时间。在紧密循环等内部,那些错过的预取可能会累加。

      【讨论】:

        【解决方案3】:

        因为如果你给它可能性,处理器不知道它应该预取哪些指令来执行。如果分支与预期不同,它必须刷新指令管道,因为那些加载的指令现在是错误的,这使得它慢了几个周期......

        【讨论】:

          【解决方案4】:

          除了预取问题,如果你在跳跃,你并没有做其他工作......

          【讨论】:

            【解决方案5】:

            如果您考虑汽车装配线,您会听到诸如 X 数量的汽车在一天内下线之类的事情。这并不意味着原材料从生产线的开头开始,X 号在一天内完成了整个运行。谁知道它可能不会,但每辆车从开始到结束可能需要几天的时间,这就是装配线的重点。想象一下,如果由于某种原因您进行了制造更改,并且您基本上必须冲洗生产线上的所有汽车并报废它们或打捞它们的零件以在其他时间放在另一辆车上。将装配线装满并恢复到每天 X 辆汽车需要一段时间。

            处理器中的指令流水线的工作方式完全相同,流水线中没有数百个步骤,但概念是相同的,以保持每个时钟周期执行一条或多条指令的速度(每天 X 辆汽车) 您需要保持该管道顺利运行。所以你预取,这会消耗一个内存周期,这通常很慢,但缓存层会有所帮助。解码,需要另一个时钟,执行,可能需要很多时钟,尤其是在像 x86 这样的 CISC 上。当您在大多数处理器上执行分支时,您必须丢弃执行和预取中的指令,如果您考虑通用的简化管道,基本上是您管道的 2/3。然后,您必须等待这些时钟进行提取,并在恢复平稳执行之前进行解码。最重要的是,根据定义,不是下一条指令,某个百分比的时间超过了一个缓存线,并且某个百分比的时间意味着从内存或更高层的缓存中获取更多时钟与线性执行相比。另一个常见的解决方案是,一些处理器声明,无论分支指令之后的任何指令是什么,或者有时分支指令之后的两条指令总是执行。这样你在刷新管道时执行,一个好的编译器会安排指令,以便在每个分支之后都有其他的 nop。懒惰的方法是在每个分支之后只放一两个 nop,创造另一个性能冲击,但对于那个平台,大多数人都会使用它。第三种方式是 ARM 所做的,具有条件执行。简而言之,前向分支并不少见,而不是说分支 if 条件,您将尝试分支的少数指令标记为 execute if not 条件,它们进入解码并执行并执行为 nops 和管道继续移动。 ARM 依赖于传统的刷新和重新填充来处理更长或向后的分支。

            较早的 x86(8088/86)手册以及其他处理器的其他同样旧的处理器手册以及微控制器手册(新旧)仍将发布执行每条指令的时钟周期。对于分支指令,如果发生分支,它会说添加 x 个时钟。您的现代 x86 甚至 ARM 和其他旨在运行 windows 或 linux 或其他(庞大而缓慢的)操作系统的处理器不会打扰,他们通常只是说它每个时钟运行一条指令或谈论 mips 到兆赫兹或类似的东西,不一定每条指令都有一个时钟表。您只需假设一个,并记住这就像每天一辆车,它是最后一个执行时钟,而不是其他时钟到达那里。特别是微控制器人员每条指令处理的不是一个时钟,并且必须比普通桌面应用程序更了解执行时间。看看其中一些的规格,Microchip PIC(不是 PIC32,它是 mips)、msp430,当然是 8051,尽管它们是由许多不同的公司制造的,它们的时序规格差异很大。

            归根结底,对于桌面应用程序甚至操作系统上的内核驱动程序,编译器的效率并不高,而且操作系统会增加更多开销,您几乎不会注意到时钟节省。切换到微控制器并放入太多分支,您的代码会慢 2 或 3 倍。即使使用编译器而不是汇编程序。使用编译器(不是用汇编程序编写)可以/将使您的代码慢 2 到 3 倍,您必须在开发、维护和可移植性与性能之间取得平衡。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2013-12-18
              • 1970-01-01
              • 2022-01-17
              • 2012-09-25
              • 1970-01-01
              • 2013-03-25
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多