繁体   English   中英

在C ++中“如果错误然后快速失败”的性能损失?

[英]Performance penalty for “if error then fail fast” in C++?

两种写入if-else的样式之间是否存在任何性能差异(在C ++中),如下所示(逻辑等效代码)对于likely1 == likely2 == true路径( likely1likely2在这里作为占位符用于更精细的一些条件)?

// Case (1):
if (likely1) {
  Foo();
  if (likely2) {
    Bar();
  } else {
    Alarm(2);
  }
} else {
  Alarm(1);
}

// Case (2):
if (!likely1) {
  Alarm(1);
  return;
}
Foo();
if (!likely2) {
  Alarm(2);
  return;
}
Bar();

我非常感谢有关尽可能多的编译器和平台的信息(但突出显示了gcc / x86)。

请注意我对这两种风格的可读性意见感兴趣,也没有任何“过早优化”声明。

编辑:换句话说,我想询问这两种样式是否在某些时候被编译器完全认为是完全100%等效/透明的(例如,在特定编译器中某些点上的逐位等效AST) ,如果没有,那么有什么区别? 对于任何(对“现代”和gcc)编译器的偏好,你知道。

而且,为了更清楚,我也真的不觉得它会给予我很大的性能提升,而且它通常会过早的优化,但感兴趣的是是否多大程度上 提高/降低任何东西?

它在很大程度上取决于编译器和优化设置。 如果差异至关重要 - 同时执行两者,并分析程序集或执行基准测试。

我对特定平台没有答案,但我可以提出一些一般观点:

  • 在没有分支预测的非现代处理器上的传统答案是,第一种可能更有效,因为在通常情况下它需要更少的分支。 但你似乎对现代编译器和处理器感兴趣。

  • 在现代处理器上,一般来说,短前向分支并不昂贵,而错误预测的分支可能很昂贵。 当然,“昂贵”意味着几个周期

  • 除此之外,编译器有权订购基本块,但它喜欢它,但前提是它不会改变逻辑。 所以当你写if (blah) {foo();} else {bar();} ,编译器有权发出如下代码:

  evaluate condition blah
  jump_if_true else_label
  bar()
  jump endif_label
else_label:
  foo()
endif_label:

总的来说,gcc倾向于按照你写的顺序大致发出东西,其他条件相同。 有各种各样的东西使其他所有不相等,例如,如果你有逻辑等价的bar(); return 在你的函数中的两个不同的地方bar(); return ,gcc可能很好地合并这些块,只发出一次调用bar()然后返回,并从两个不同的地方跳转或掉到那个。

  • 分支预测有两种 - 静态和动态。 静态意味着分支的CPU指令指定条件是否“可能”,以便CPU可以针对常见情况进行优化。 编译器可能会在某些平台上发出静态分支预测,如果您正在针对该平台进行优化,则可以编写代码来考虑这一点。 您可以通过了解编译器如何处理各种控制结构或使用编译器扩展来考虑它。 就个人而言,我认为它不足以概括编译器将会做什么。 看看反汇编。

  • 动态分支预测意味着在热代码中,CPU将自己统计分支的可能性,并针对常见情况进行优化。 现代处理器使用各种不同的动态分支预测技术: http//en.wikipedia.org/wiki/Branch_predictor 性能关键代码几乎热代码,只要动态分支预测策略有效,它就可以非常快速地优化热代码。 可能有某些病理情况混淆了特定的策略,但总的来说,你可以说任何处于紧张循环中的任何偏向于采取/不采取的情况,将在大多数情况下被正确预测

  • 有时甚至无论分支是否正确预测都是无关紧要的,因为某些情况下某些CPU在等待条件评估时会在指令管道中包含这两种可能性,并抛弃不必要的选项。 现代CPU变得复杂 然而,更简单的CPU设计可以避免分支成本,例如ARM上的条件指令。

  • 无论如何,对其他功能的调用将会扰乱所有这些猜测。 因此在您的示例中可能存在细微差别,这些差异可能取决于Foo,Bar和Alarm中的实际代码。 不幸的是,不可能区分显着和微不足道的差异,或者考虑到这些功能的细节,而不会进入你不感兴趣的“过早优化”指控。

  • 微观优化尚未编写的代码几乎总是为时过早。 很难预测名为Foo和Bar的函数的性能。 据推测,问题的目的是辨别是否存在应该为编码风格提供信息的常见问题。 答案是,由于动态分支预测,没有。 在热门代码中,条件的排列方式几乎没有什么区别,并且它确实产生差异,差异不像“在if条件下采取/不采用分支更快”这样容易预测。

  • 如果这个问题只适用于单个程序,并且该代码被证明是热门的,那么它当然可以进行测试,没有必要进行概括。

它取决于编译器。 查看__builtin_expect上的gcc文档。 您的编译器可能有类似的东西。 请注意,您确实应该关注过早优化。

答案很大程度上取决于“可能”的类型。 如果它是一个整型常量表达式,编译器可以对其进行优化,两种情况都是等价的。 否则,它将在运行时进行评估,并且无法进行太多优化。

因此,情况2通常比情况1更有效。

作为我使用的实时嵌入式系统的输入,您的“案例2”通常是安全和/或性能至关重要的代码的标准。 安全关键型嵌入式系统的样式指南通常允许使用此语法,因此函数可以在出错时快速退出。

通常,样式指南会对“case 2”语法不屑一顾,但是如果允许在一个函数中允许多次返回则允许异常

1)该功能需要快速退出并处理错误,或

2)如果函数末尾的单个返回导致代码不太可读,这通常是各种协议和数据解析器的情况。

如果您关注性能,我假设您正在使用配置文件引导优化。

如果您使用的是配置文件引导优化,则您提出的两种变体完全相同。

无论如何,你所询问的内容的性能完全被代码示例中不明显的性能特征所掩盖,所以我们真的无法回答这个问题。 你必须测试两者的性能。

虽然我和其他所有人在一起,因为优化分支是没有意义的,如果没有分析并且实际上已经找到了瓶颈......如果有的话,优化可能的情况是有意义的。

正如他们的名字所示,可能1和可能2都可能。 因此排除两者都可能的组合也可能是最快的:

if(likely1 && likely2)
{
    ... // happens most of the time
}else
{
    if(likely1)
        ...
    if(likely2)
        ...
    else if(!likely1 && !likely2) // happens almost never
        ...
}

请注意,第二个else可能不是必需的,一个像样的编译器会发现,如果前一个if子句不是真的,那么即使你没有明确告诉它,也是如此。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM