“IF”价格昂贵吗?

时间:2008-11-24 20:18:33

标签: if-statement language-agnostic branch-prediction

对于我的生活,我不能记住那天我们老师说的话,我希望你可能知道。

该模块是“数据结构和算法”,他告诉我们一些类似的内容:

  

if声明是最贵的   [某件事]。 [东西]注册   [东西]。

是的,我确实有一段可怕的记忆,我真的很抱歉,但我一直在谷歌搜索几个小时,什么都没有出现。有什么想法吗?

16 个答案:

答案 0 :(得分:164)

在最低级别(在硬件中),是,如果是昂贵的。为了理解原因,您必须了解pipelines的工作原理。

要执行的当前指令存储在通常称为指令指针(IP)或程序计数器(PC)的内容中;这些术语是同义词,但不同的术语与不同的体系结构一起使用。对于大多数指令,下一条指令的PC只是当前PC加上当前指令的长度。对于大多数RISC架构,指令都是恒定长度,因此PC可以增加一个恒定的量。对于像x86这样的CISC架构,指令可以是可变长度的,因此解码指令的逻辑必须弄清楚当前指令找到下一条指令位置的时间。

但是,对于分支指令,下一条要执行的指令不是当前指令之后的下一个位置。分支是有问题的 - 它们告诉处理器下一条指令的位置。分支可以是有条件的也可以是无条件的,目标位置可以是固定的也可以是计算的。

条件与无条件很容易理解 - 只有在某个条件成立时才会采用条件分支(例如一个数字是否等于另一个);如果未采用分支,则控制在正常分支之后进入下一条指令。对于无条件分支,始终采用分支。条件分支显示在if语句和forwhile循环的控制测试中。无条件分支出现在无限循环,函数调用,函数返回,breakcontinue语句,臭名昭着的goto语句等等(这些列表远非详尽无遗)。

分支目标是另一个重要问题。大多数分支都有一个固定的分支目标 - 它们在编译时修复的代码中的特定位置。这包括if语句,各种循环,常规函数调用等等。 Computed 分支在运行时计算分支的目标。这包括switch语句(有时),从函数返回,虚函数调用和函数指针调用。

那么这对性能意味着什么呢?当处理器看到分支指令出现在其管道中时,它需要弄清楚如何继续填充其管道。为了弄清楚程序流中分支之后的指令,它需要知道两件事:(1)是否采用分支和(2)分支的目标。解决这个问题称为branch prediction,这是一个具有挑战性的问题。如果处理器正确猜测,程序将以全速继续运行。如果相反,处理器错误地猜测 ,它只花了一些时间来计算错误的东西。它现在必须刷新其管道并使用正确的执行路径中的指令重新加载它。底线:性能大打击。

因此,if语句昂贵的原因是分支误预测。这只是最低级别。如果您正在编写高级代码,则根本不需要担心这些细节。如果您在C或汇编中编写极其性能关键的代码,那么您应该只关心这一点。如果是这种情况,编写无分支代码通常优于分支代码,即使需要更多指令也是如此。您可以使用一些很酷的比特技巧来计算诸如abs()min()max()之类的内容而无需分支。

答案 1 :(得分:16)

“昂贵”是一个非常相对的术语,尤其是与“if”语句的关系,因为您还必须考虑条件的成本。这可以是从几个简短的cpu指令到测试调用远程数据库的函数的结果。

我不担心。除非您正在进行嵌入式编程,否则您可能根本不应该关注“if”的成本。对于大多数程序员而言,它不会永远成为应用程序性能的驱动因素。

答案 2 :(得分:14)

分支机构,尤其是RISC架构微处理器,是一些最昂贵的指令。这是因为在许多体系结构中,编译器会预测最有可能执行的执行路径并将这些指令放在可执行文件中,因此当分支发生时它们已经位于CPU缓存中。如果分支走向另一个方向,它必须返回主存并获取新指令 - 这相当昂贵。在许多RISC架构中,除分支(通常为2个周期)外,所有指令都是一个周期。我们不是在讨论这里的主要成本,所以不要担心。此外,编译器将比99%的时间更好地优化:)关于EPIC架构(Itanium是一个例子)的一个非常棒的事情是它从分支的两端缓存(并开始处理)指令,然后在知道分支的结果后丢弃它不需要的集合。这可以节省典型架构在沿着不可预测的路径分支时的额外内存访问。

答案 3 :(得分:10)

查看有关Cell Performance的文章Better Performance Through Branch Elimination。另一个有趣的是实时碰撞检测博客上的this post about branchless selections

除了针对这个问题已经发布的优秀答案之外,我还想提醒一下,虽然“if”语句被认为是昂贵的低级操作,但试图利用无分支编程技术。更高级别的环境,例如脚本语言或业务逻辑层(不论语言),可能是非常不合适的。

绝大多数情况下,程序应首先清晰编写,然后针对性能进行优化。有许多问题领域,其中性能至关重要,但简单的事实是,大多数开发人员不是在渲染引擎的核心深处编写模块,也不是在几周内运行的高性能流体动力学模拟。当你的解决方案“正常工作”的首要任务时,最后一件事就是你是否可以节省代码中条件语句的开销。

答案 4 :(得分:7)

在尽可能低的级别if包含(在计算特定if的所有特定于应用程序的先决条件后):

  • 一些测试说明
  • 如果测试成功,
  • 跳转到代码中的某个位置,否则继续前进。

与此相关的成本:

  • 低级别比较 - 通常是1次cpu操作,超级便宜
  • 潜在的跳跃 - 这可能很昂贵

Reson为什么跳跃很贵:

  • 你可以跳转到存在于内存中任何位置的arbirary代码,如果事实证明它没有被cpu缓存 - 我们有问题,因为我们需要访问主内存,这是较慢的
  • 现代CPU做分支整理。他们试图猜测是否会成功并在管道中提前执行代码,从而加快速度。如果预测失败,则必须使管道提前完成的所有计算无效。这也是一项昂贵的操作

总结一下:

  • 如果你真的,真的,非常关心表现,那么可能会有所体现。
  • 您应该关心它当且仅当您正在编写实时光线跟踪器或生物模拟或类似的东西时。在大多数现实世界中没有理由关心它。

答案 5 :(得分:7)

if本身慢。慢慢总是相对的,我打赌我的生活,你从来没有感受到if语句的“开销”。如果你打算制作一个高性能的代码,那么你无论如何都要避免分支。使if变慢的原因是处理器基于某些启发式和诸如此类的东西在if之后预加载代码。它还将阻止管道在机器代码中的if分支指令之后直接执行代码,因为处理器还不知道将采用什么路径(在流水线处理器中,多个指令被交错并执行)。执行的代码可能必须反向执行(如果采用了另一个分支。它被称为branch misprediction),或者在这些地方填充noop,这样就不会发生这种情况。

如果if是邪恶的,那么switch也是邪恶的,&&||也是如此。别担心。

答案 6 :(得分:6)

分支可能会导致CPU指令被预取吗?

答案 7 :(得分:6)

现代处理器具有较长的执行流水线,这意味着可以同时在不同阶段执行多条指令。当下一个指令开始运行时,它们可能并不总是知道一条指令的结果。当它们遇到条件跳转(如果)时,它们有时必须等到管道为空才能知道指针指针应该走哪条路。

我认为这是一个长途货运列车。它可以在一条直线上快速运载大量货物,但它的转弯很严重。

奔腾4(普雷斯科特)拥有31个阶段的着名长管道。

更多关于Wikipedia

答案 8 :(得分:4)

我可以想象的唯一可能是指if语句通常可以导致分支。根据处理器体系结构的具体情况,分支可能导致流水线停滞或其他不太理想的情况。

然而,这是特定情况 - 大多数现代处理器具有分支预测功能,试图最小化分支的负面影响。另一个例子是ARM架构(可能还有其他)如何处理条件逻辑 - ARM具有指令级条件执行,因此简单的条件逻辑不会导致分支 - 如果不满足条件,指令只是作为NOP执行。

所有这一切 - 在担心这些事情之前让你的逻辑正确。不正确的代码就像你可以得到的那样没有优化。

答案 9 :(得分:4)

正如许多人所指出的,现代计算机上的条件分支可能非常慢。

话虽如此,有很多条件分支不存在于if语句中,你不能总是告诉编译器会提出什么,并且担心基本语句需要多长时间才能实现做错了。 (如果你可以告诉编译器可靠地生成什么,你可能没有一个好的优化编译器。)

答案 10 :(得分:3)

CPU非常流畅。任何分支指令(如果/ for / while / switch / etc)意味着CPU实际上不知道要加载和运行的指令。

CPU在等待知道该做什么时会停止,或者CPU会猜测。对于较旧的CPU,或者如果猜测错误,您将不得不遇到管道停顿并加载正确的指令。根据CPU的不同,这可能会高达10-20条指令。

现代CPU试图通过做好分支预测,同时执行多条路径,只保留实际路径来避免这种情况。这有很大帮助,但只能到目前为止。

祝好在课堂上好运。

此外,如果您在现实生活中不得不担心这一点,那么您可能正在进行操作系统设计,实时图形处理,科学计算或类似CPU限制的操作。在担心之前简介。

答案 11 :(得分:3)

另请注意,循环内部必然非常昂贵。

现代CPU在第一次访问if语句时假定要采用“if-body”(或者说另一种方式:它还假设循环体被多次采用)(*)。在第二次和进一步访问时,它(CPU)可以查看分支历史表,并查看条件是最后一次(是真的吗?是假的吗?)。如果最后一次是假的,那么推测性执行将进入if的“else”或超出循环。

(*)规则实际上是“未采取前向分支,采用后向分支”。在if语句中,如果条件的计算结果为false,则 <跳转到 body 之后的点(记住:CPU无论如何)假设不采取分支/跳转),但在循环中,可能有一个前向分支到循环后的位置(不被采用),并且在重复时被采用后向分支(将被采用)。

这也是为什么对虚函数或函数指针调用的调用没有那么多假设(http://phresnel.org/blog/

的原因之一

答案 12 :(得分:1)

以最清晰,最简单,最干净的方式编写程序,这显然效率不高。这可以最有效地利用最昂贵的资源。无论是写作还是以后调试(需要理解)程序。如果性能不够,请衡量瓶颈所在的位置,并了解如何减轻这些瓶颈。只有在非常罕见的情况下,您才会在执行此操作时担心个别(来源)说明。性能是关于在第一行中选择正确的算法和数据结构,仔细编程,获得足够快的机器。使用一个好的编译器,当看到现代编译器的代码重构时,你会感到惊讶。为性能重构代码是一种最后的手段,代码变得更复杂(因此更加笨拙),更难修改,因此全面更昂贵。

答案 13 :(得分:0)

我和我的一个朋友曾经有过这个争论。他使用的是一种非常天真的圆形算法,但声称他比我快(这种只计算圆的1/8),因为我使用了if。最后,if语句被替换为sqrt,不知怎的更快。也许是因为FPU内置了sqrt?

答案 14 :(得分:0)

某些CPU(如X86)为编程级别提供分支预测,以避免这种分支预测延迟。

某些编译器将这些(如GCC)公开为高级编程语言(如C / C ++)的扩展。

参考likely()/unlikely() macros in the Linux kernel - how do they work? What's their benefit?

答案 15 :(得分:-1)

ALU使用方面最贵?它使用CPU寄存器来存储要比较的值,并且每次运行if语句时都需要花时间来获取和比较值。

因此,优化是进行一次比较,并在循环运行之前将结果存储为变量。

试着解释你遗失的单词。