重复间接的表现

时间:2014-12-31 19:40:58

标签: java c++ performance repeat indirection

我发现自己在讨论是否要编写代码1和代码2之类的代码。在我看来,代码1看起来更清晰,但理论上,由于与代码2相比的额外间接性,我是否可以期待性能损失?这里有任何相关的编译器优化吗?如果bar()返回Bar *?

,是否会发生任何变化

代码1:

foo.bar().method1();
foo.bar().method2();
foo.bar().method3();
foo.bar().method4();

代码2:

Bar& bar = foo.bar(); //Java programmers: ignore ampersand
bar.method1();
bar.method2();
bar.method3();
bar.method4();

编辑: 我认为有太多的变量要问这样一个普遍的问题(例如const与非const方法,编译器是否内联方法,编译器如何处理引用等)。在程序集中分析我的特定代码也许是要走的路。

5 个答案:

答案 0 :(得分:1)

与Code_2相比,Code_1似乎有性能损失。

但请记住强大的C ++设计的最基本规则: - 过早优化是所有邪恶的根源。为了清晰起见,请先编写代码,然后指定一个好的分析器作为“Guru”。

答案 1 :(得分:1)

第二个选项Bar bar = foo.bar()肯定更有效率,但多少取决于重量条的重量。这种差异很可能是微不足道的;尝试基准测试。

至于可读性,我认为第二种选择更具可读性,但这是个人风格。我认为你真正想要的是method5,它在内部调用所有四种方法。因此你可以拥有

foo.bar().method5();

就是这样。

答案 2 :(得分:1)

取决于bar实际执行的操作,可能会有(或明显的)性能损失。 一个更有趣的问题是,是什么让你认为你的第一种方法是更清洁"。

在不知道任何实现细节的情况下,我实际上倾向于认为相反:后者的方法不仅更短(短的是好的,因为更少的代码是更少的错误,更少的东西阅读),但也更清洁和更多可读。

它清楚地反映了作者的意图,并没有让读者想知道bar的实施细节,这可能导致意料之外的副作用,而这反过来可能是也可能不是故意的和/或期望。

除非你有充分的理由,否则不要这样做。

答案 3 :(得分:0)

参考测试

我跑了一个简单的测试。当使用 no optimizations 编译时,在我的机器上,Test_1耗时1272 ms,Test_2 1108(我运行了几次测试,结果在几毫秒内完成)。 使用O2 / O3优化,两个测试似乎花费相同的时间:946毫秒。

    #include <iostream>
    #include <stdio.h>
    #include <stdlib.h>
    #include <time.h>
    #include <chrono>

    using namespace std;

    class Foo
    {
    public:
      Foo() : x_(0) {}
      void add(unsigned amt)
      {
        x_ += amt;
      }
      unsigned x_;
    };

    class Bar
    {
    public:
      Foo& get()
      {
        return foo_;
      }
    private:
      Foo foo_;
    };

    int main()
    {
      srand(time(NULL));
      Bar bar;
      constexpr int N = 100000000;
      //Foo& foo = bar.get(); //TEST_2
      auto start_time = chrono::high_resolution_clock::now();
      for (int i = 0; i < N; ++i)
      {
        bar.get().add(rand()); //TEST_1
        //foo.add(rand()); //TEST_2
      }
      auto end_time = chrono::high_resolution_clock::now();

      cout << bar.get().x_ << endl;
      cout << "Time: ";
      cout << chrono::duration_cast<chrono::milliseconds>(end_time - start_time).count() << endl;
    }

指针测试

我重新尝试了测试,但这次是将类成员作为指针。使用 no optimizations 进行编译时,在我的机器上,Test_3需要1285-1340 ms,Test_4需要1110 ms。 使用O2 / O3优化,两个测试似乎花费相同的时间:915毫秒(令人惊讶的是,时间少于上面的参考测试)。

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <chrono>

using namespace std;

class Foo
{
public:
  Foo() : x_(0) {}
  void add(unsigned amt)
  {
    x_ += amt;
  }
  unsigned x_;
};

class Bar
{
public:
  ~Bar()
  {
    delete foo_;
  }
  Foo* get()
  {
    return foo_;
  }
private:
  Foo* foo_ = new Foo;
};

int main()
{
  srand(time(NULL));
  Bar bar;
  constexpr int N = 100000000;
  //Foo* foo = bar.get(); //TEST_4
  auto start_time = chrono::high_resolution_clock::now();
  for (int i = 0; i < N; ++i)
  {
    bar.get()->add(rand()); //TEST_3
    //foo->add(rand()); //TEST_4
  }
  auto end_time = chrono::high_resolution_clock::now();

  cout << bar.get()->x_ << endl;
  cout << "C++ Time: ";
  cout << chrono::duration_cast<chrono::milliseconds>(end_time - start_time).count() << endl;
}

<强>结论

根据我机器上的这些简单测试,当优化时,Code 2样式稍微快了约15%左右,但启用了优化后,性能没有差异。

答案 4 :(得分:0)

我认为,在大多数情况下,这两种选择都不是一个好选择。两者都通过参考暴露内部构件,这破坏了封装。我认为Foo对象的抽象级别对于它的使用来说太低了,它应该提供更多类似服务的函数来对它做些什么。

而不是

Bar& bar = foo.bar(); //Java programmers: ignore ampersand
bar.method1(); 
bar.method2(); 
bar.method3(); 
bar.method4();

Foo中应该有一些更高级别的方法

class Foo
{
public:
    void doSomething()
    {
        Bar& bar = foo.bar(); //Java programmers: ignore ampersand
        bar.method1(); 
        bar.method2(); 
        bar.method3(); 
        bar.method4();
    }

private:
    Bar& bar();
};

你永远不应该向客户提供对内部的非const引用。