2

为了了解类组合的运行时性能,我编写了以下测试代码。在其中,我比较了将函数作为类的成员函数直接调用所花费的时间,与通过将原始类作为成员的复合类调用它所花费的时间。

看起来这些方法应该花费相当的时间,但它们没有:通过复合类调用几乎需要两倍的时间。

这是代码:

const int REPS(1e8);
const double INPUT(5.29);

class Base {
public:
  inline double BaseFunc(double x) const;
};

double Base::BaseFunc(double x) const {
  return 2.718*x + 3.14;
};

class Super {
public:
  inline double BaseFunc(double x) const;
private:
  Base b_; 
};

double Super::BaseFunc(double x) const {
  return b_.BaseFunc(x);
};

int main() {
  auto t0 = std::chrono::high_resolution_clock::now();

  // Construct objects.
  Base b;
  Super s;

  // Call to base directly.
  for (int i = 0; i < REPS; ++i)
    b.BaseFunc(INPUT);
  auto t1 = std::chrono::high_resolution_clock::now();

  // Call to base through composited class.
  for (int i = 0; i < REPS; ++i)
    s.BaseFunc(INPUT);
  auto t2 = std::chrono::high_resolution_clock::now();

  // Find average durations.
  auto diff1 = std::chrono::duration_cast<std::chrono::nanoseconds>(t1-t0).count();
  diff1 /= REPS;
  auto diff2 = std::chrono::duration_cast<std::chrono::nanoseconds>(t2-t1).count();
  diff2 /= REPS;

  std::cout << "Calling directly to base took " << diff1 << "nsec.\n";
  std::cout << "Calling to base through a composited class took " << diff2 << "nsec.\n";
}

使用 g++ 版本 4.7.2 编译,使用 -std=c++11 -O0 -Winline,我得到:

Calling directly to base took 13nsec.
Calling to base through a composited class took 24nsec.

为什么这两种调用本质上相同的函数的方式之间存在如此差异?我认为由于所有内容都是内联的(gcc 没有告诉我其他情况),因此这些应该是同一件事。

我在想这个完全不正确吗?任何帮助表示赞赏!谢谢!

更新感谢所有帮助!我返回并在函数调用中添加更多内容(在向量上调用 inner_product)并使用每次重复的所有结果,以确保 gcc 没有优化任何东西。然后我打开优化。你们都是对的:差异消失了。

我想我学到了两件重要的事情:1)关闭优化后,gcc 甚至不会尝试内联,因此 -Winline 标志毫无意义,2)这两种调用函数的方式之间没有有意义的区别。我可以自信地从其他类中调用成员函数!

再次感谢!

4

3 回答 3

6

该程序未经优化编译 ( -O0)。这意味着我们不能期望通常的代码质量。为了找出问题所在,我们需要查看生成的代码。可能没有发生内联(尽管使用 请求inline)。这可能使运行时调用的数量增加了一倍,因此运行时间大约增加了一倍。

如果程序经过适当优化,两个循环都将被完全删除。我认为这个基准是没有实际意义的,无论结果如何,它都是毫无意义的。在生产中,将执行全面优化和实际工作负载。

于 2013-08-21T16:36:38.753 回答
1

inline只是编译器的提示,而不是要求。事实上,大多数编译器都忽略了它。

您提供了编译器参数-O0,禁用了所有优化。由于内联是一种优化,因此不会发生。这意味着调用开销发生了两次——解释了几乎两倍的运行时间。

于 2013-08-21T16:37:50.187 回答
0

要查看函数调用是否内联,您可以查看生成的汇编代码。在 Linux 上,您可以使用它objdump -d来执行此操作。如果您不想遍历所有程序集,并且知道要查找的内容,则可以 grep 查找有意义的内容:

objdump -d a.out | egrep "(main|BaseFunc|call)" | egrep "(main|BaseFunc)" | egrep -v "(jmp|jne)"

(是的,如果重写,几乎肯定可以缩短 egrep 正则表达式链,但这不是重点。)

如果函数未内联,结果将类似于以下内容:

08048700 <__libc_start_main@plt>:
 804879c:       e8 5f ff ff ff          call   8048700 <__libc_start_main@plt>
080488c0 <main>:
 804896e:       e8 65 02 00 00          call   8048bd8 <_ZNK4Base8BaseFuncEd>
 80489b5:       e8 50 02 00 00          call   8048c0a <_ZNK5Super8BaseFuncEd>
08048bd8 <_ZNK4Base8BaseFuncEd>:
08048c0a <_ZNK5Super8BaseFuncEd>:
 8048c30:       e8 a3 ff ff ff          call   8048bd8 <_ZNK4Base8BaseFuncEd>

请注意对名为(和)main的函数的两次调用。BaseFunc()Base::BaseFunc()Super::BaseFunc()

我把你改成-O0然后-O1我看到这样的东西......

08048660 <__libc_start_main@plt>:
 80486dc:       e8 7f ff ff ff          call   8048660 <__libc_start_main@plt>
08048800 <main>:

...现在您可以看出这两个函数调用都是内联的,并且(在这种情况下,很可能)甚至 REPS 循环本身也可能已被优化掉,因为在我的系统上两个结果都是 0ns。

为了防止循环优化,我添加了这个全局变量:

volatile long gCtr = 0;

...在函数Base::BaseFunc()中,我在之前添加了这个return

  gCtr += 1;

...现在两者的结果都是 2ns,这似乎是可信的。这两个BaseFunc()函数都是内联的,但循环没有被优化掉。

于 2013-08-21T17:23:40.300 回答