对于我的一生,我无法记住那天老师确切说了些什么,我希望您可能会知道。
该模块是“数据结构和算法”,他告诉我们以下内容:
这
if
句话是最昂贵的。[某物]注册[某物]。
是的,我的记忆确实很糟糕,真的很抱歉,但是我已经搜寻了几个小时,没有任何反应。有任何想法吗?
对于我的一生,我无法记住那天老师确切说了些什么,我希望您可能会知道。
该模块是“数据结构和算法”,他告诉我们以下内容:
这
if
句话是最昂贵的。[某物]注册[某物]。
是的,我的记忆确实很糟糕,真的很抱歉,但是我已经搜寻了几个小时,没有任何反应。有任何想法吗?
Answers:
在最低的级别(在硬件中),是的,如果 s昂贵。为了理解原因,您必须了解管道如何工作。
当前要执行的指令存储在通常称为指令指针(IP)或程序计数器(PC)的东西中。这些术语是同义词,但是不同的体系结构使用了不同的术语。对于大多数指令,下一条指令的PC就是当前PC加上当前指令的长度。对于大多数RISC架构,指令都是固定长度的,因此PC可以递增恒定量。对于x86之类的CISC体系结构,指令可以是可变长度的,因此对指令进行解码的逻辑必须弄清楚当前指令要找到多长时间才能找到下一条指令的位置。
但是,对于分支指令,要执行的下一条指令不是当前指令之后的下一个位置。分支是指令-它们告诉处理器下一条指令在哪里。分支可以是有条件的,也可以是无条件的,目标位置可以是固定的或计算的。
有条件与无条件比较容易理解-只有在满足某个条件(例如一个数字是否等于另一个数字)的情况下,才进行条件分支。如果不执行分支,则控制像往常一样前进到分支之后的下一条指令。对于无条件分支,总是采用分支。条件分支显示在if
语句和for
and while
循环的控制测试中。无条件分支出现在无限循环,函数调用,函数返回break
和continue
语句,臭名昭著的goto
语句等中(这些列表远非详尽)。
分支机构目标是另一个重要问题。大多数分支都有固定的分支目标-它们会转到编译时固定的代码中的特定位置。这包括if
语句,各种循环,常规函数调用等等。 计算分支在运行时计算分支的目标。这包括switch
语句(有时),从函数返回,虚拟函数调用和函数指针调用。
那么,这一切对性能意味着什么?当处理器看到分支指令出现在其管道中时,它需要弄清楚如何继续填充其管道。为了弄清楚程序流中的分支后面有什么指令,它需要知道两件事:(1)是否将采用分支;(2)分支的目标。弄清楚这一点称为分支预测,这是一个具有挑战性的问题。如果处理器猜对了,程序将以全速继续。相反,如果处理器猜测正确,它只是花了一些时间计算错误的事情。现在,它必须刷新其管道,并从正确的执行路径中重新加载指令。底线:表现出色。
因此,语句昂贵的原因是由于分支的错误预测。这仅处于最低级别。如果您正在编写高级代码,则完全不必担心这些细节。仅当您使用C或汇编语言编写对性能至关重要的代码时,才应考虑这一点。在这种情况下,即使需要更多指令,编写无分支代码通常也比分支代码更好。有一些很酷位变换花样,你可以做计算的东西,如abs()
,min()
和max()
没有分支。
“昂贵”是一个非常相对的术语,尤其是与“ if
”语句的关系,因为您还必须考虑条件的成本。范围从任何简短的cpu指令到测试调出远程数据库的函数的结果。
我不用担心。除非您正在进行嵌入式编程,否则您可能根本不必担心“ if
” 的成本。对于大多数程序员而言,它永远不会成为应用程序性能的驱动因素。
分支,尤其是在RISC体系结构微处理器上的分支,是一些最昂贵的指令。这是因为在许多体系结构上,编译器会预测最可能采用哪个执行路径,并将这些指令放在可执行文件的下一个位置,因此当分支发生时,它们将已经在CPU缓存中。如果分支的执行方向相反,则必须返回主内存并获取新指令-这相当昂贵。在许多RISC架构上,除分支(通常为2个周期)外,所有指令均为一个周期。我们这里不是在讨论重大成本,所以不用担心。此外,编译器将在99%的时间内比您更好地进行优化:)关于EPIC架构(Itanium就是一个例子)的真正令人敬畏的事情之一是,它从分支的两侧缓存(并开始处理)指令,然后在分支的结果为众所周知。如果它沿意外路径分支,则可以节省典型体系结构的额外内存访问。
查看文章“通过消除单元性能来提高性能”。另一个有趣的地方是有关实时碰撞检测博客上的无分支选择的文章。
除了针对该问题已经发布的出色答案外,我想提醒一下,尽管“ if”语句被认为是昂贵的低级操作,但尝试在更高级别的环境中利用无分支编程技术诸如脚本语言或业务逻辑层(无论使用哪种语言)之类的信息,可能是非常荒谬的。
在绝大多数情况下,应首先为清晰起见编写程序,然后为性能进行优化。在许多问题领域中,性能是最重要的,但是一个简单的事实是,大多数开发人员都没有编写要在渲染引擎的核心中使用的模块,也不是连续运行数周的高性能流体动力学仿真的模块。当您的解决方案“工作正常”是当务之急时,您想到的最后一件事应该是您是否可以节省代码中条件语句的开销。
if
本身并不慢。我一生都认为,缓慢总是相对的,因为您从未感到过if语句的“开销”。如果您要编写高性能代码,则可能无论如何都要避免分支。使if
速度变慢的原因是,处理器是if
基于某种启发式方法和诸如此类的方法从之后预加载代码。它还会if
在机器代码中的分支指令之后停止流水线直接执行代码,因为处理器尚不知道将采用哪种路径(在流水线处理器中,多条指令被交错并执行)。执行的代码可能必须反向执行(如果采用了另一个分支。它称为branch misprediction
),或者noop
在这些位置填充,以免发生这种情况。
如果if
是邪恶的,则switch
是太邪恶,和&&
,||
太。不用担心
在最低的层次上if
包含(在计算了特定的所有特定于应用的先决条件之后if
):
与此相关的费用:
反思为什么跳伞很昂贵:
所以总结一下:
也许分支杀死了CPU指令的预取?
还要注意,在循环中是不是一定是非常昂贵的。
现代CPU假定在第一次访问if语句时就将采用“ if-body”(或换句话说:它还假定要对循环体进行多次)(*)。在第二次及以后的访问中,它(CPU)可以查看“ 分支历史记录表”,并查看上次情况如何(是否为真?是否为假?)。如果最后一次为假,则推测执行将继续进行if的“ else”操作,否则将超出循环范围。
(*)规则实际上是“ 不采用前向分支,采用后向分支 ”。在if语句中,如果条件的计算结果为false(记住:CPU始终假定不进行分支/跳转),则只有 [向前]跳转(到达if-body之后的点)。 ,则可能在循环之后的位置有一个前向分支(不采取),在重复执行时有一个向后分支(要采取)。
这也是为什么对虚拟函数的调用或对函数指针的调用不会像许多人想象的那样糟糕的原因之一(http://phresnel.org/blog/)
CPU深入地流水线化。任何分支指令(如果/ for / while / switch / etc)都意味着CPU并不真正知道下一步要加载和运行的指令。
CPU在等待知道要做什么时停顿,或者CPU猜测。如果使用的是较旧的CPU,或者猜测不正确,则必须在流水线停顿并加载正确的指令时遭受它的困扰。根据CPU的不同,它可能高达10-20条指令的停顿值。
现代CPU尝试通过执行良好的分支预测,同时执行多个路径并仅保留实际路径来避免这种情况。这有很大帮助,但只能走得很远。
班上祝你好运。
另外,如果您在现实生活中不得不担心这一点,则可能是在进行OS设计,实时图形,科学计算或类似的CPU限制操作。担心之前先介绍一下。
一些CPU(如X86)将分支预测提供到编程级别,以避免这种分支预测延迟。
一些编译器将它们(如GCC)公开为高级编程语言(如C / C ++)的扩展。
我曾经和我的一个朋友有过这样的争论。他使用的是非常幼稚的圆算法,但声称自己比我的算法(仅计算圆的1/8的算法)要快,因为我的算法是if。最后,将if语句替换为sqrt并以某种方式更快。也许是因为FPU内置了sqrt?
就ALU使用而言,最昂贵的?它占用CPU寄存器来存储要比较的值,并在每次运行if语句时花费时间来获取和比较值。
因此,优化的方法是进行一次比较,然后在循环运行之前将结果存储为变量。
只是试图解释您遗漏的单词。