在这种情况下,是不是有比 if-else 更快的替代方法?
Posted
技术标签:
【中文标题】在这种情况下,是不是有比 if-else 更快的替代方法?【英文标题】:Is there a faster alternative to if-else in this case?在这种情况下,是否有比 if-else 更快的替代方法? 【发布时间】:2011-01-13 15:41:49 【问题描述】:while(some_condition)
if(FIRST)
do_this;
else
do_that;
在我的程序中,if(FIRST)
成功的可能性约为 10000 分之一。C/C++ 中是否有任何替代方案,这样我们就可以避免在 while 循环内的每次迭代中检查条件,希望看到更好的在这种情况下的表现。
好的!让我更详细地介绍一下。 我正在为信号采集和跟踪方案编写代码,其中我的系统状态将更频繁地保持在 TRACKING 模式而不是 ACQUISITION 模式。
while(signal_present)
if(ACQUISITION_SUCCEEDED)
do_tracking(); // this functions can change the state from TRACKING to ACQUISITION
else
do_acquisition(); // this function can change the state from ACQUISITION to TRACKING
所以这里发生的情况是,系统通常保持在跟踪模式,但在跟踪失败时可以进入采集模式,但这并不常见。(假设传入的数据数量是无限的。)
【问题讨论】:
关键是FIRST
是什么?
鉴于现代 CPU 架构和性能,您可以做的任何可能的优化在软件的整个生命周期中所节省的时间可能比我撰写和发布此评论所花费的时间要少。
@David:从所有愚蠢的应用程序和内核代码来看,其中包含丑陋的 gcc __builtin_expect
废话,我觉得这个问题超出了 SO 的范围......
@R.. 我想知道什么样的程序在if
语句上存在性能瓶颈;它不能做很多有用的工作!
@Meeir:你分析过代码吗?配置文件说瓶颈在哪里?
【参考方案1】:
单个分支的性能成本不会是什么大问题。你唯一能做的就是把最有可能的代码放在第一位,节省一些指令缓存。也许。这对微优化非常深入。
【讨论】:
【参考方案2】:没有特别好的理由尝试优化它。几乎所有现代架构都包含branch predictors。这些推测分支(if 或 else)将基本上按照过去的方式进行。在您的情况下,推测将始终成功,消除所有开销。有一些不可移植的方式来暗示一个条件以一种或另一种方式被采用,但任何分支预测器都可以正常工作。
改善指令缓存局部性可能需要做的一件事是将 do_that
移出 while 循环(除非它是函数调用)。
【讨论】:
【参考方案3】:GCC 有一个__builtin_expect
“函数”,您可以使用它向编译器指示可能会采用哪个分支。你可以这样使用它:
if(__builtin_expect(FIRST, 1)) …
这有用吗?我不知道。我从未使用过它,从未见过它使用过(据称在 Linux 内核中除外)。 GCC 文档实际上不鼓励使用它,而是支持使用分析信息来获得更可靠的指标。
【讨论】:
它在 Linux 和 GNU 软件中被广泛使用,除了混淆代码之外没有其他用途。最糟糕的是,他们倾向于#define LIKELY(x) __builtin_expect((x)!=0, 1)
,然后写if(LIKELY(condition))
,它错误地用简单的英语谓词表示法读作“如果条件可能”,这让以前没有见过这个成语的人感到困惑。跨度>
【参考方案4】:
在最近的 x86 处理器系统上,最终执行速度将几乎不依赖源代码实现。
您可以查看此页面http://igoro.com/archive/fast-and-slow-if-statements-branch-prediction-in-modern-processors/ 以了解处理器内部发生的优化程度。
【讨论】:
你的第一句话完全脱离现实。在测试strstr
的朴素算法时,我能够通过对循环计数器进行看似毫无意义的变化、选择索引/指针表示法的混合等来将性能改变 50% 或更多。这是在 gcc 4 上。 x 和一个 Atom cpu。【参考方案5】:
如果与do_aquisition
的实现相比,此测试确实花费了大量时间,那么您可能会通过函数表获得提升:
typedef void (*trackfunc)(void);
trackfunc tracking_action[] = do_acquisition, do_tracking;
while (signal_present)
tracking_action[ACQUISITION_STATE]();
这些手动优化的效果很大程度上取决于平台、编译器和优化设置。
通过花时间测量和调整 do_aquisition 和 do_tracking 算法,您很可能会获得更大的性能提升。
【讨论】:
您的嵌套while()
循环将执行与 OP 的原始循环一样多的 ACQUISITION_SUCCEEDED
测试。【参考方案6】:
如果您不知道“FIRST”何时为真,那么不。
问题在于 FIRST 是否耗时;也许您可以在循环(或部分循环)之前评估 FIRST 并测试布尔值。
【讨论】:
【参考方案7】:我会把月影的代码稍微改一下
while( some_condition )
do_that;
if( FIRST )
do_this; // overwrite what you did earlier.
【讨论】:
你可能是对的。这个问题很难回答,甚至不知道 FIRST 是什么类型的变量。【参考方案8】:根据您的新信息,我会说以下内容:
while(some_condition)
while(ACQUISITION_SUCCEEDED)
do_tracking();
if (some_condition)
while(!ACQUISITION_SUCCEEDED)
do_acquisition();
关键是ACQUISITION_SUCCEEDED
状态必须在一定程度上包含some_condition
信息(即,如果some_condition
为假,它将突破内部循环-因此有机会突破外循环)
【讨论】:
好的。我会试试这个,看看它是否比旧代码有改进。谢谢 这看起来不一样。如果some_condition
在第一个内部循环中发生变化,但ACQUISITION_SUCCEEDED
没有变化怎么办?
@Steve,这就是为什么我说“ACQUISITION_SUCCEEDED
状态必须包含some_condition
信息”....
@Nim:如果some_condition
被合并到ACQUISITION_SUCCEEDED
中,它怎么会是假的,导致ACQUISITION_SUCCEEDED
是假的(根据需要打破第一个内循环),并且还有 case !ACQUISITION_SUCCEEDED
为假(根据需要打破第二个循环)?某种没有足够括号的可怕宏,所以!仅绑定到与some_condition
不同的ACQUISITION_SUCCEEDED
部分?
@Steve,我用!ACQUISITION_SUCCEEDED
表示另一种情况,这并不意味着它是不是第一个条件的字面意思,我们正在假设究竟是什么ACQUISITION_SUCCEEDED
。我的意思是,这里有一个非常简单的布尔逻辑 - 如果 AS
条件在每个函数调用时都更新(显然它可能是),那么将另一个布尔值合并到 AS
标志有什么困难?上面有人有类似的答案,添加了一个额外的&&
,这完全是多余的,只要你注意它的更新方式,你只需要检查一个布尔值。【参考方案9】:
这是优化的经典之作。如果可以的话,你应该避免将条件放在循环中。这段代码:
while(...)
if( a )
foo();
else
bar();
通常最好重写为:
if( a )
while(...)
foo();
else
while(...)
bar();
但这并不总是可行的,当您尝试优化某些东西时,您应该始终测量前后的性能。
【讨论】:
【参考方案10】:没有比您的示例更有用的优化了。
对do_this
和do_that
的调用/分支可能会抵消您通过优化if-then-else
语句获得的任何节省。
性能优化的规则之一是减少分支。大多数处理器更喜欢执行顺序代码。他们可以获取大量顺序代码并将其拖入缓存。分支中断了这种愉快,并可能导致指令缓存的完全重新加载(这会失去宝贵的执行时间)。
在此级别进行微优化之前,请检查您的设计,看看是否可以:
-
消除不必要的分支。
拆分代码使其适合
缓存。
组织数据以减少提取
从内存或硬盘驱动器。
我确信上述步骤将比优化您发布的循环获得更多的性能。
【讨论】:
以上是关于在这种情况下,是不是有比 if-else 更快的替代方法?的主要内容,如果未能解决你的问题,请参考以下文章
Java 中是不是有比 Xalan/Xerces 更快的 XML 解析器 [关闭]
jQuery:在这种情况下(.find)vs(.filter)更快[关闭]
是否有比删除其目录更快/更好的方法来清除 iPhone 模拟器缓存?