“中频”贵吗?


98

对于我的一生,我无法记住那天老师确切说了些什么,我希望您可能会知道。

该模块是“数据结构和算法”,他告诉我们以下内容:

if句话是最昂贵的。[某物]注册[某物]。

是的,我的记忆确实很糟糕,真的很抱歉,但是我已经搜寻了几个小时,没有任何反应。有任何想法吗?


29
请问老师一个选择吗?
迈克尔·迈尔斯

7
您为什么不给老师发电子邮件?SO上的任何人不太可能知道您的老师说的话,除非当时他们在那儿(或者您的老师自己读SO的话)。
Bill Karwin

11
当然,这是与强制性铁路答案
bobobobo

可以通过在x86和arm处理器上执行特殊的条件执行指令来实现受C影响的大括号语言中的if语句或特别是“?:”表达式。这些是根据先前测试执行或不执行某些操作的指令。使用这些出色的指令完全不需要条件跳转/跳转/“ goto”指令。在某些情况下,通过使程序流完全可预测,因为它只是连续进行,而没有(可能是不可预测的)跳转到代码中的不同点,因此可以极大地提高性能。
塞西尔·沃德

一个好的编译器有时可能需要朝正确的方向推动一点,以便它通过重组代码并可能在表达式或?中使用聪明的算术,而使用条件指令而不是笨拙并使用条件跳转。:表达。除非您真的了解您的asm并阅读过例如Agner Fog的优化指南,否则请不要使用它。不管是否使用语句或编译器,有时编译器都会正确处理它。:使用表达式。
塞西尔·沃德

Answers:


185

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

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

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

有条件与无条件比较容易理解-只有在满足某个条件(例如一个数字是否等于另一个数字)的情况下,才进行条件分支。如果不执行分支,则控制像往常一样前进到分支之后的下一条指令。对于无条件分支,总是采用分支。条件分支显示在if语句和forand while循环的控制测试中。无条件分支出现在无限循环,函数调用,函数返回breakcontinue语句,臭名昭著的goto语句等中(这些列表远非详尽)。

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

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

因此,语句昂贵的原因是由于分支的错误预测。这仅处于最低级别。如果您正在编写高级代码,则完全不必担心这些细节。仅当您使用C或汇编语言编写对性能至关重要的代码时,才应考虑这一点。在这种情况下,即使需要更多指令,编写无分支代码通常也比分支代码更好。有一些很酷位变换花样,你可以做计算的东西,如abs()min()max()没有分支。


20
这不只是分支误预测。分支还会在编译器级别以及某种程度上在CPU级别(当然,对于乱序的CPU)禁止指令重新排序。不错的详细答案。
jalf

5
如果最终将高级语言转换为低级语言,并且您正在编写以性能为中心的代码,那么编写避免if语句的代码是否仍然一无所获?这个概念不会融入高级语言吗?
ç..

18

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

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


1
绝对是相对的... cmp / cond jmp在许多处理器上仍然比mul更快。
Brian Knoblauch

4
是的,我同意我不应该对此担心。我没有在这里尝试优化任何东西。我只是想寻找和学习。;)
pek

15

分支,尤其是在RISC体系结构微处理器上的分支,是一些最昂贵的指令。这是因为在许多体系结构上,编译器会预测最可能采用哪个执行路径,并将这些指令放在可执行文件的下一个位置,因此当分支发生时,它们将已经在CPU缓存中。如果分支的执行方向相反,则必须返回主内存并获取新指令-这相当昂贵。在许多RISC架构上,除分支(通常为2个周期)外,所有指令均为一个周期。我们这里不是在讨论重大成本,所以不用担心。此外,编译器将在99%的时间内比您更好地进行优化:)关于EPIC架构(Itanium就是一个例子)的真正令人敬畏的事情之一是,它从分支的两侧缓存(并开始处理)指令,然后在分支的结果为众所周知。如果它沿意外路径分支,则可以节省典型体系结构的额外内存访问。


13

查看文章“通过消除单元性能来提高性能”。另一个有趣的地方是有关实时碰撞检测博客上的无分支选择的文章。

除了针对该问题已经发布的出色答案外,我想提醒一下,尽管“ if”语句被认为是昂贵的低级操作,但尝试在更高级别的环境中利用无分支编程技术诸如脚本语言或业务逻辑层(无论使用哪种语言)之类的信息,可能是非常荒谬的。

在绝大多数情况下,应首先为清晰起见编写程序,然后为性能进行优化。在许多问题领域中,性能是最重要的,但是一个简单的事实是,大多数开发人员都没有编写要在渲染引擎的核心中使用的模块,也不是连续运行数周的高性能流体动力学仿真的模块。当您的解决方案“工作正常”是当务之急时,您想到的最后一件事应该是您是否可以节省代码中条件语句的开销。


确实!也许还会有人补充说,当使用一种鼓励调用的语言进行编码(基本上是除stdlib之外的汇编程序或C语言以外的任何其他语言)时,来自常规编程技术的管道干扰将使有关条件分支的所有问题不堪重负。
罗斯·帕特森

10

if本身并不慢。我一生都认为,缓慢总是相对的,因为您从未感到过if语句的“开销”。如果您要编写高性能代码,则可能无论如何都要避免分支。使if速度变慢的原因是,处理器是if基于某种启发式方法和诸如此类的方法从之后预加载代码。它还会if在机器代码中的分支指令之后停止流水线直接执行代码,因为处理器尚不知道将采用哪种路径(在流水线处理器中,多条指令被交错并执行)。执行的代码可能必须反向执行(如果采用了另一个分支。它称为branch misprediction),或者noop在这些位置填充,以免发生这种情况。

如果if是邪恶的,则switch是太邪恶,和&&||太。不用担心


7

在最低的层次上if包含(在计算了特定的所有特定于应用的先决条件之后if):

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

与此相关的费用:

  • 比较低的水平-通常1 cpu操作,超级便宜
  • 潜在的跳跃-可能会很昂贵

反思为什么跳伞很昂贵:

  • 如果事实证明它没有被cpu缓存,则可以跳转到驻留在内存中任何位置的原始代码-我们遇到了问题,因为我们需要访问主内存,速度较慢
  • 现代CPU会执行分支掠夺。他们尝试猜测是否会成功并在管道中提前执行代码,从而加快处理速度。如果预测失败,则必须使流水线前面进行的所有计算无效。那也是一个昂贵的手术

所以总结一下:

  • 如果可以,那么,如果您真的,真的,认真地关心性能。
  • 当且仅当您正在编写实时raytracer或生物模拟或类似的东西时,才应该关心它。在大多数现实世界中,没有理由关心它。

将其带入下一个层次:嵌套和/或复合if语句怎么办?如果有人编写了很多这样的if语句,那么花费很快就会变得很明显。而且对于大多数开发人员而言,如果语句看起来像是这样的基本操作,那么避免繁琐的条件分支通常会引起样式问题。风格方面的关注仍然很重要,但是在当前的激烈讨论中,它们可能是第一个被忽略的关注。
jaydel

7

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

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

奔腾4(普雷斯科特(Prescott))有一段著名的漫长流水线,共有31个阶段。

更多关于维基百科


3
+1是货运列车的隐喻-我会记住,下一次我需要解释处理器管道。
丹尼尔·普里登

6

也许分支杀死了CPU指令的预取?


通过我的...“研究”,我了解了关于switch语句的跳转表和分支,但对于if语句则一无所知。您能对此进行详细说明吗?
pek

在IIRC中,CPU通常沿一条可能的执行路径预取指令,但是如果'if'语句从预测的执行路径中引起分支,则它将使预取的指令无效,并且必须重新启动预处理。
activout.se

任何体面的处理器都应具有分支预测功能,该功能将尝试猜测是否将采用分支,并基于该预测进行预取指令(通常相当不错)。GCC甚至具有C扩展名,这些扩展名允许程序员为分支预测变量提供提示。
mipadi

2
而且,CPU通常会提前开始执行即将执行的指令(而不仅仅是预取它们),并且编译器会尝试对指令进行重新排序,这在分支之间变得很危险,因此您真的可以取消过多分支的指令调度。这会影响性能。
jalf

6

还要注意,在循环中是不是一定是非常昂贵的。

现代CPU假定在第一次访问if语句时就将采用“ if-body”(或换句话说:它还假定要对循环体进行多次)(*)。在第二次及以后的访问中,它(CPU)可以查看“ 分支历史记录表”,并查看上次情况如何(是否为真?是否为假?)。如果最后一次为假,则推测执行将继续进行if的“ else”操作,否则将超出循环范围。

(*)规则实际上是“ 不采用前向分支,采用后向分支 ”。在if语句中,如果条件的计算结果为false(记住:CPU始终假定不进行分支/跳转),则只有 [向前]跳转(到达if-body之后的点)。 ,则可能在循环之后的位置有一个前向分支(不采取),在重复执行时有一个向后分支(要采取)。

这也是为什么对虚拟函数的调用或对函数指针的调用不会像许多人想象的那样糟糕的原因之一(http://phresnel.org/blog/


5

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

话虽这么说,但是if语句中没有很多条件分支,您不能总是知道编译器将要处理什么,而担心基本语句将花费多长时间几乎总是错误的事情。去做。(如果您可以确定编译器将可靠生成的内容,则可能没有良好的优化编译器。)


4

我可以想象的唯一一件事是,if语句通常可以导致分支。根据处理器体系结构的具体情况,分支会导致流水线停顿或其他非最佳情况。

但是,这是非常特殊的情况-大多数现代处理器具有分支预测功能,试图将分支的负面影响最小化。另一个示例是ARM体系结构(可能还有其他)如何处理条件逻辑-ARM具有指令级的条件执行,因此简单的条件逻辑导致没有分支-如果不满足条件,则指令将作为NOP执行。

所有这些-在担心这些事情之前弄清楚您的逻辑。错误的代码会尽可能地得到优化。


我听说ARM的条件指令会禁止ILP,因此它们可能只是在解决问题。
JD 2012年

3

CPU深入地流水线化。任何分支指令(如果/ for / while / switch / etc)都意味着CPU并不真正知道下一步要加载和运行的指令。

CPU在等待知道要做什么时停顿,或者CPU猜测。如果使用的是较旧的CPU,或者猜测不正确,则必须在流水线停顿并加载正确的指令时遭受它的困扰。根据CPU的不同,它可能高达10-20条指令的停顿值。

现代CPU尝试通过执行良好的分支预测,同时执行多个路径并仅保留实际路径来避免这种情况。这有很大帮助,但只能走得很远。

班上祝你好运。

另外,如果您在现实生活中不得不担心这一点,则可能是在进行OS设计,实时图形,科学计算或类似的CPU限制操作。担心之前先介绍一下。


2

以最清晰,最简单,最简洁的方式编写程序,但这显然效率不高。您可以充分利用最昂贵的资源。无论是编写程序还是稍后调试(需要理解)。如果性能不够,请测量瓶颈在哪里,并查看如何缓解它们。仅在极少数情况下,您才需要担心单个(源)指令。性能就是在第一行中选择正确的算法和数据结构,精心编程,以获取足够快的计算机。使用一个好的编译器,当您看到现代编译器进行的代码重组时,您会感到惊讶。为了提高性能而对代码进行重组是一种不得已的措施,代码变得更加复杂(因此,buggier),更难修改,因此整体成本更高。



0

我曾经和我的一个朋友有过这样的争论。他使用的是非常幼稚的圆算法,但声称自己比我的算法(仅计算圆的1/8的算法)要快,因为我的算法是if。最后,将if语句替换为sqrt并以某种方式更快。也许是因为FPU内置了sqrt?


-1

就ALU使用而言,最昂贵的?它占用CPU寄存器来存储要比较的值,并在每次运行if语句时花费时间来获取和比较值。

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

只是试图解释您遗漏的单词。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.