【问题标题】:Will the compiler be able to inline this method?编译器是否能够内联此方法?
【发布时间】:2017-03-21 07:20:45
【问题描述】:

当前数据结构

我有一个代码,其中数据结构分别由类A1A2 的两个向量表示。

vector<A1> v1;
vector<A2> v2;

我的代码中缓慢的部分(大约占总运行时间的 80%)在于迭代这些向量,并对 v1 的元素和 funA2 的元素应用 funA1 方法 v2

double result = 0;
for (int i = 0 ; i < v1.size() ; i++)
{
  results *= v1[i].funA1();
}
for (int i = 0 ; i < v2.size() ; i++)
{
  results *= v2[i].funA2();
}

重塑数据结构

虽然数据结构使用这两个向量,但直觉上宁愿使用混合数据类型A1A2 的单个向量。使用两个不同的向量会导致代码中其他地方的可读性和维护性下降。

我想我可以将它们合并成一个向量。想法是使父类AA1A2 兄弟姐妹类,并使用vector&lt;A&gt; v 作为单个向量。我会在A 类中定义一个虚拟fun 并在A1A2 中覆盖它,这样我就可以这样做了

double result = 0;
for (int i = 0 ; i < v.size() ; i++)
{
  results *= v[i].fun();
}

问题

在第一种情况下,方法funA1funA2可以被编译器内联(实际上我自己内联了它们,因为它们是非常简单的函数)。

在第二种情况下,恐怕内联是不可能的。它是否正确?

编译器会设法内联方法fun吗?

当然,性能损失可能可以忽略不计,我应该对其进行测试,但我想知道在尝试进行所有修改之前是否值得先验。

【问题讨论】:

标签: c++ performance oop vector inline


【解决方案1】:

答案是肯定的“也许”。判断编译器是否确实内联调用的唯一方法是编译它,然后查看程序集列表。这一切都取决于fun 的复杂性(其中“复杂性”是一个非常特定于编译器的指标)。

就个人而言,除非这是您的应用程序中时间最关键的部分,否则我不会担心。函数调用并不昂贵。与往常一样,为了清晰和可读性而编写代码,然后再进行优化。

【讨论】:

  • 我同意:分析是深入了解您的程序实际花费大部分时间的最佳方式(因此您应该将精力集中在哪里)。
【解决方案2】:

简短的回答是“不”。您要求具有不同类型的向量实例不利于编译器能够内联虚函数调用。

假设A1A2 派生自A,则A1A2 的实例不能安全地存储到std::vector&lt;A&gt; 中。这样做的结果将是对象切片(例如,仅将每个对象的 A 部分复制到 std::vector&lt;A&gt; 中)。如果A 是一个抽象类(即有任何纯虚函数),这样的东西将无法编译。如果A 不是抽象的,那么std::vector&lt;A&gt; 中所有对象的静态类型为A - 因此调用v[i].fun() 将为每个元素调用A::fun()。所以A1::fun()A2::fun() 永远不会被调用。

如果您将对象的地址存储在std::vector&lt;A *&gt; 中,那么v[i]-&gt;fun() 将根据对象的实际类型调用虚函数。然而,这不能被编译器内联,因为要调用的函数是在运行时根据向量中每个对象的实际类型确定的(即编译器不知道每个对象的类型)。如果您使用 std::vector&lt;std::unique_ptr&lt;A&gt;&gt;,则类似 cmets。

注意:在std::vector&lt;A *&gt;std::vector&lt;std::unique_ptr&lt;A&gt;&gt; 中存储指向多态类A 的指针还有其他问题,例如A 需要有一个虚拟析构函数,以便对象的生命周期被正确管理,等等。如果你打算使用这种方法,你需要考虑这些事情 - 否则结果将是未定义的行为。

如果您想帮助优化代码的性能,您需要提供更多信息(例如,关于您如何分析的信息、AA1A2 的类型、功能是什么正在做,等等)。

【讨论】:

  • 我更期待“不”而不是“可能”,所以我赞成!但是,我当然注意到到目前为止的答案之间存在相互矛盾的信念。我很欣赏额外的解释。谢谢
【解决方案3】:

您提议将A::fun 设为虚拟,并有两个覆盖A1::funA2::fun,进行一次调用,然后期望编译器内联两者 A1::fun 和@987654325 @ 在那一次通话中?

这是乐观的。不过也不是不可能。

【讨论】:

    【解决方案4】:

    从计算速度的角度来看,最好使用单独的向量。内联虚函数需要去虚拟化,尽管编译器经常这样做,但很难提供任何保证。

    除此之外,还有缓存(指令和数据)、预取器、分支预测器——所有这些都可以从同构遍历中受益。

    如果您绝对需要合并向量,您可以尝试将一个附加到另一个,这样一种类型的对象就会聚集在一起。也许您甚至可以对大向量的两个部分进行两次单独的遍历,并避免 virtaul 调用。

    在任何情况下,您最好的选择是尽可能地分析所有内容,并确定性能损失是否可以接受(或是否明显)。

    正如@Someprogrammerdude 提到的,std::accumulate 在查看您的代码时会浮现在脑海中:)

    【讨论】:

      【解决方案5】:

      编译器会在 Class 中内联简单函数。例如:一个函数只有返回。所以对于你的问题,我认为没有。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-05-19
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多