【问题标题】:Why is this code slower even if the function is inlined?为什么即使函数是内联的,这段代码也会变慢?
【发布时间】:2010-02-02 20:49:46
【问题描述】:

我有这样的方法:

bool MyFunction(int& i)
{
  switch(m_step)
  {
    case 1:
       if (AComplexCondition)
       {
         i = m_i;
         return true;
       }

    case 2:
      // some code

    case 3:
      // some code
  }
}

由于 case 语句很多(超过 3 个)并且函数越来越大,我尝试将 case 1 中的代码提取出来,并将其放入这样的内联函数中:

inline bool funct(int& i)
{
  if (AComplexCondition)
  {
    i = m_i;
    return true;
  }
  return false;
}
bool MyFunction(int& i)
{
  switch(m_step)
  {
    case 1:
       if (funct(i))
       {
         return true;
       }

    case 2:
      // some code

    case 3:
      // some code
  }
}

似乎这段代码比原来的要慢得多。 我检查了 -Winline 并且函数是内联的。 为什么这段代码比较慢?我认为这将是等效的。我看到的唯一区别是第二个版本中多了一个条件检查,但我认为编译器应该能够优化它。对吧?

编辑: 有些人建议我应该使用 gdb 来检查两个版本中的每个汇编指令以查看差异。我这样做了。

第一个版本是这样的:

mov
callq (Call to AComplexCondition())
test
je (doesn't jump)
mov (i = m_i)
movl (m_step = 1)

第二个版本,慢了一点,看起来更简单。

movl (m_step = 1)
callq (Call to AComplexCondition())
test
je (doesn't jump)
mov (i = m_i)
xchg %ax,%ax (This is a nop I think)

这两个版本似乎做同样的事情,所以我仍然不知道为什么第二个版本仍然较慢。

【问题讨论】:

  • 您的 case 块没有 break -- 这是故意的吗?
  • @GMan :我比较了整个应用程序在此更改前后的执行时间。在此更改之后,整个应用程序的速度降低了 2.5%(是的,在这种情况下,2.5% 是一个大问题)。
  • @OP:实时,2.5%是多少,时钟测时有多准确?
  • @Earlz :整个应用程序的执行时间增加了 2.5%。我没有单独测试这个函数,但由于它只是内部循环的一小部分,我猜它会慢得多。无论如何,对我来说,关键是我不明白为什么编译器不会为这两个版本产生等效的输出。
  • 我想应该问一下:你的优化级别是多少? -03?

标签: c++ performance gcc refactoring inline


【解决方案1】:

一步一步来。种下断点,进入反汇编视图,开始单步执行。

所有的谜团都会消失。

【讨论】:

  • 好建议。我这样做了(请参阅我对 OP 的编辑),但它没有帮助,可能是因为我不太了解我所看到的。或者我没有正确使用 gdb。
【解决方案2】:

这很难追踪。一个问题可能是代码膨胀导致大部分循环被推出(小)CPU缓存......但现在我想到它也不完全有意义......

我建议做什么:

尽可能地隔离代码和条件,同时仍然能够观察到减速。

然后,去分析它。剖析有意义吗?现在,(假设您准备好冒险)分解代码并查看 g++ 的不同之处。在此处报告这些结果

【讨论】:

    【解决方案3】:

    GMan 是正确的,inline 不保证你的函数会被内联。这是对编译器的一个提示,它可能是一个好主意。如果编译器认为内联函数不明智,那么您现在就有了函数调用的开销。这至少意味着要执行两个 JMP 语句。这意味着函数的指令存储在非顺序位置,而不是调用函数的下一个内存位置,执行将移动该新位置完成它并移回函数调用之后。

    【讨论】:

    • 看我的帖子。我验证并确认该函数是由 gcc 内联的。
    • 确实,我只是确保他知道这一点。我和 Mathieu 一样困惑,为什么性能会改变;编译器应该能够生成相同的代码。
    • 我发帖时没有看到 cmets。
    【解决方案4】:

    没有看到 ComplexCondition 部分,很难说。如果该条件足够复杂,编译器将无法正确对其进行流水线处理,并且会干扰芯片中的分支预测。只是一种可能。

    【讨论】:

    • 是的,条件很复杂并且暗示调用其他函数(在某些情况下)但是 ComplexCondition 在两个版本的代码中是相同的,所以我认为它会产生相同的影响。我说的对吗?
    • 取决于您使用的编译器。 GCC 在内联之前进行指令调度,而 Visual Studio 在 IIRC 之后进行。
    【解决方案5】:

    汇编器会告诉你发生了什么吗?看反汇编可能比让我们猜测更容易,尽管我通常同意 iaimtomisbehave 的 jmp 想法。

    【讨论】:

      【解决方案6】:

      这是个好问题。让我们知道你发现了什么。我确实有一些想法,主要是由于编译器不再能够分解您内联的代码,但没有保证的答案。

      1. 语句顺序。编译器将此语句与其复杂代码放在最后是有道理的。这意味着将首先评估其他案例,除非必要,否则永远不会对其进行检查。如果您简化语句,它可能不会这样做,这意味着您的疯狂条件每次都会得到全面评估。

      2. 创建额外的案例。应该可以从 if 语句中提取一些附加条件,并在某些情况下进行额外的 case 语句。这可以消除一些检查。

      3. 流水线失败。即使它内联,它也无法分解实际内联中的代码。这是所有这三个问题的基本问题,但是对于流水线,这显然会导致问题,因为对于流水线,您希望在进行检查之前开始执行。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-09-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多