【问题标题】:Will an if statement with an unchanging condition slow down my C++ code?条件不变的 if 语句会减慢我的 C++ 代码吗?
【发布时间】:2020-04-01 18:57:57
【问题描述】:

我正在求解一个耦合常微分方程系统,在我的程序运行之间,我希望更改要积分的函数。到目前为止,我只是简单地注释和取消注释三个返回语句以在不同的函数之间切换,如下所示:

arma::vec acceleration(arma::vec u, double t)
{   /*
    The current acceleration of the system.
    */

    // return acceleration_1(u);
    // return acceleration_2(u);
    return acceleration_3(u);
}

acceleration被多次调用,我想让代码高效,但我也想更改与输入参数集成的函数,而不是通过输入代码的深度来注释和取消- 评论不同的陈述。

如果我要发送一个参数,比如choice,它只能是{1, 2, 3} 之一,并且choice 在程序运行期间永远不会改变,if 语句会减慢我的程序吗?示例:

arma::vec acceleration(arma::vec u, double t, int choice)
{   /*
    The current acceleration of the system.
    */

    if (choice == 1)
    {
        return acceleration_1(u);

    }

    else if (choice == 2)
    {
        return acceleration_2(u);

    }

    else if (choice == 3)
    {
        return acceleration_3(u);

    }
}

switch 语句可能更适合(更快)用于此目的吗?任何不会减慢代码速度的替代选项?

编辑:accelerationacceleration_x 是成员函数,它们使具有函数指针的替代解决方案变得困难。

【问题讨论】:

  • 你是在每秒调用一百万次吗?您是否对代码进行了性能分析?是性能瓶颈吗?
  • @Eljay 该函数每秒被调用大约 150 万次。我没有对代码进行广泛的分析,只有几个简单的时间。
  • 如果您有可能在编译时将choice 设为constexpr,那么您很可能不会受到任何性能损失

标签: c++ optimization scientific-computing


【解决方案1】:

简短的回答是,是的,如果您将它与根本没有if 语句进行比较,它就会出现。但这不是你想要的答案。

您的程序已编译并在 CPU 上运行。您没有提及您的项目针对哪种 CPU 架构,但大多数现代 CPU 使用分支预测,当条件在运行时可能发生变化时,这有助于加速幼稚条件检查的情况。

即使在你的情况下,正如你所说,选择变量在程序生命周期内不会改变,如果没有分支预测,编译器和 CPU 也没有太多机会避免每次你指示时检查它你的源代码。它是一个运行时变量,对于 C++,没有任何结构可以帮助编译器或 CPU 知道它何时会改变。他们可能会使用巧妙的启发式方法——比如用调用调度(函数调用)替换大量 if 语句,但如果您执行以下操作,那将是相同的:

arma::vec (*chosen_acceleration_proc)(arma::vec, double);

arma::vec acceleration(arma::vec u, double t)
{
    return chosen_acceleration_proc(u, t);
}

您可以在运行时切换所选程序:

chosen_acceleration_proc = acceleration_1; /// For example

以上内容将阻止编译器inlining 您的不同加速程序,否则它可能会。但是,acceleration 过程的唯一额外成本是调用 chosen_acceleration_proc。完成所选加速过程的成本可能远远超过调用它的成本。这里的底线是,如果加速过程可以从内联中受益,那么使用过程指针是一种不合适的方法。

您必须对已编译的程序进行概要分析,以查看此类调度的成本是否可以忽略不计或很大。

回到分支预测,对于具有分支预测的 CPU,CPU 在做出相同选择一定次数后会“学习”:

arma::vec acceleration(arma::vec u, double t) {
    switch(acceleration_proc_id) {
        case 1: return acceleration_1(u, t);
        case 2: return acceleration_2(u, t);
        /// etc
    }
}

...这意味着当您将acceleration_proc_id 设置为某个选定的值时,一段时间后,CPU 将假设它将保持不变并预取并推测性地执行指令,就好像该选择是给定的一样。请参阅this most excellent answer 了解有关分支预测及其如何帮助或失败的更多信息。

switch 几乎总是比使用ifelse 更好的选择,当然是你的情况。这部分是因为人们可能只使用常量表达式来打开,这对于优化编译器很有用。有other reasons

如果不同的过程都是一个类的所有成员怎么办?

如果不同的加速计算过程属于同一类或其派生类,您仍然可以使用指向成员过程的指针。如果他们都是具有相同签名的不同成员,在同一个类中:

class Foo {
    arma::vec acceleration1(arma::vec u, double t);
    arma::vec acceleration2(arma::vec u, double t);
    arma::vec acceleration3(arma::vec u, double t);
}

arma::vec (Foo::*chosen_acceleration_proc)(arma::vec u, double t);

Foo obj;

chosen_acceleration_proc = &Foo::acceleration2; /// Specify your preferred procedure

arma::vec acceleration(arma::vec, double t) {
    return (obj.*chosen_acceleration_proc)(u, t);
}

不管你如何设计你的应用程序——无论它是一个包含不同加速过程的“应用程序”对象还是应用了一些其他语义,你都可以像上面那样将指针指向所需的过程,这不是真的与“普通”程序不同。

总而言之,CPU 可以运行的最佳代码是完全没有代码——如果您需要在运行时切换或调用实际的、非内联的过程,则成本会在那里,而不是使用预处理器或常量表达式来决定程序编译时你想要的加速功能。

您最终需要分析使用switch 的程序与使用过程指针的程序,或者您能想到的任何其他我没有介绍的方法。无论如何,分析将确定 CPU 大部分时间花在哪里。如果在分析之后您得出结论,与实际计算加速相比,能够在运行时决定一次加速过程的成本可以忽略不计,那么只需编写您可以编写的最易读、最短和最简单的代码,其余的留给编译器——这是它的工作。

【讨论】:

  • 感谢您的详细解答。我将对不同的解决方案进行一些分析。由于acceleration_x 是成员函数,您建议的过程切换解决方案可能不起作用。我已将信息添加到主帖中。
  • 如果它们是成员函数,没有什么真正改变,除了你也可以使用虚拟成员函数设计你的解决方案。要我编辑答案吗?
  • @BobsonDugnut 他们是同一个班级的成员吗?
  • 所有的加速度都是同一类的成员,是的。我看到你已经更新了答案。我现在将尝试您的解决方案并进行一些分析。谢谢。
猜你喜欢
  • 2016-10-16
  • 2016-02-28
  • 1970-01-01
  • 2013-01-12
  • 2018-02-11
  • 2020-06-06
  • 1970-01-01
相关资源
最近更新 更多