关于unique_ptr表演

时间:2011-11-15 14:56:10

标签: c++ gcc c++11 unique-ptr

我经常读到,在大多数情况下,unique_ptr优先于shared_ptr,因为unique_ptr是不可复制的并且具有移动语义; shared_ptr会因复制和引用计数而增加开销;

但是当我在某些情况下测试unique_ptr时,它看起来比它的对手明显更慢(在访问中)

例如,在 gcc 4.5

编辑:打印方法实际上不打印任何内容

#include <iostream>
#include <string>
#include <memory>
#include <chrono>
#include <vector>

class Print{

public:
void print(){}

};

void test()
{
 typedef vector<shared_ptr<Print>> sh_vec;
 typedef vector<unique_ptr<Print>> u_vec;

 sh_vec shvec;
 u_vec  uvec;

 //can't use initializer_list with unique_ptr
 for (int var = 0; var < 100; ++var) {

    shared_ptr<Print> p(new Print());
    shvec.push_back(p);

    unique_ptr<Print> p1(new Print());
    uvec.push_back(move(p1));

  }

 //-------------test shared_ptr-------------------------
 auto time_sh_1 = std::chrono::system_clock::now();

 for (auto var = 0; var < 1000; ++var) 
 {
   for(auto it = shvec.begin(), end = shvec.end(); it!= end; ++it)
   {
     (*it)->print();
   }
 }

 auto time_sh_2 = std::chrono::system_clock::now();

 cout <<"test shared_ptr : "<< (time_sh_2 - time_sh_1).count() << " microseconds." << endl;

 //-------------test unique_ptr-------------------------
 auto time_u_1 = std::chrono::system_clock::now();

 for (auto var = 0; var < 1000; ++var) 
 {
   for(auto it = uvec.begin(), end = uvec.end(); it!= end; ++it)
   {
     (*it)->print();
   }
 }

 auto time_u_2 = std::chrono::system_clock::now();

 cout <<"test unique_ptr : "<< (time_u_2 - time_u_1).count() << " microseconds." << endl;

}

平均而言,我得到(g ++ -O0):

  • shared_ptr:1480微秒
  • unique_ptr:3350微秒

区别来自哪里?它可以解释吗?

3 个答案:

答案 0 :(得分:19)

于2014年1月1日更新

我知道这个问题很老,但结果仍然适用于G ++ 4.7.0和libstdc ++ 4.7。所以,我试图找出原因。

您在此处进行基准测试的是使用 -O0 解除引用效果,并查看unique_ptrshared_ptr的实施情况,结果实际上是正确的。

unique_ptr将指针和删除器存储在::std::tuple中,而shared_ptr直接存储裸指针句柄。因此,当您取消引用指针(使用*, - &gt;或get)时,您需要额外调用::std::get<0>()中的unique_ptr。相反,shared_ptr直接返回指针。 即使在优化和内联时,在gcc-4.7上,:: std :: get&lt; 0&gt;()比直接指针慢一些。。优化和内联时,gcc-4.8.1完全省略了:: std :: get&lt; 0&gt;()的开销。在我的机器上,当用-O3编译时,编译器生成完全相同的汇编代码,这意味着它们字面相同。

总而言之,使用当前的实现, shared_ptr在创建,移动,复制和引用计数方面较慢,但同样快 * 解除引用 *。

注意print()在问题中为空,编译器在优化时省略了循环。所以,我稍微改变了代码以正确观察优化结果:

#include <iostream>
#include <string>
#include <memory>
#include <chrono>
#include <vector>

using namespace std;

class Print {
 public:
  void print() { i++; }

  int i{ 0 };
};

void test() {
  typedef vector<shared_ptr<Print>> sh_vec;
  typedef vector<unique_ptr<Print>> u_vec;

  sh_vec shvec;
  u_vec uvec;

  // can't use initializer_list with unique_ptr
  for (int var = 0; var < 100; ++var) {
    shvec.push_back(make_shared<Print>());
    uvec.emplace_back(new Print());
  }

  //-------------test shared_ptr-------------------------
  auto time_sh_1 = std::chrono::system_clock::now();

  for (auto var = 0; var < 1000; ++var) {
    for (auto it = shvec.begin(), end = shvec.end(); it != end; ++it) {
      (*it)->print();
    }
  }

  auto time_sh_2 = std::chrono::system_clock::now();

  cout << "test shared_ptr : " << (time_sh_2 - time_sh_1).count()
       << " microseconds." << endl;

  //-------------test unique_ptr-------------------------
  auto time_u_1 = std::chrono::system_clock::now();

  for (auto var = 0; var < 1000; ++var) {
    for (auto it = uvec.begin(), end = uvec.end(); it != end; ++it) {
      (*it)->print();
    }
  }

  auto time_u_2 = std::chrono::system_clock::now();

  cout << "test unique_ptr : " << (time_u_2 - time_u_1).count()
       << " microseconds." << endl;
}

int main() { test(); }

注意:这不是一个基本问题,可以通过在当前的libstdc ++实现中放弃使用:: std :: tuple来轻松修复。

答案 1 :(得分:12)

您在定时块中所做的只是访问它们。这根本不会涉及任何额外的开销。增加的时间可能来自控制台输出滚动。你永远不可能在定时基准测试中做I / O.

如果你想测试引用计数的开销,那么实际上做一些引用计数。如果你从不改变shared_ptr ,那么shared_ptr的构建,破坏,分配和其他变异操作的时间增加会如何影响你的时间?

编辑:如果没有I / O,那么编译器优化在哪里?他们应该完成整个事情。甚至连想法都让这个人大吃一惊。

答案 2 :(得分:3)

你没有在这里测试任何有用的东西。

你在说什么:复制

您正在测试的内容:迭代

如果要测试副本,实际上需要执行副本。两个智能指针在阅读时应该具有相似的性能,因为良好的shared_ptr实现将保持指向对象的本地副本。

修改

关于新元素:

一般来说,使用调试代码时甚至不值得谈论速度。如果您关心性能,您将使用版本代码(通常为-O2)因此应该测量,因为调试代码和发布代码之间可能存在显着差异。最值得注意的是,内联模板代码可以大大减少执行时间。

关于基准:

  • 我会添加另一轮措施:裸指针。通常,unique_ptr和裸指针应该具有相同的性能,值得检查它,并且在调试模式下不一定是真的。
  • 您可能希望“交错”两个批次的执行,或者如果不能,请在几个运行中取每个批次的平均值。实际上,如果计算机在基准测试结束时速度变慢,那么只有unique_ptr批次会受到影响,这会影响测量。

你可能有兴趣从Neil那里学到更多东西:The Joy of Benchmarks,这不是一个明确的指南,但它很有趣。特别是关于强制副作用以避免死码删除的部分;)

另外,要小心你的测量方法。时钟的分辨率可能不如它看起来那么精确。如果时钟仅以每15us刷新一次,则15us左右的任何测量都是可疑的。在测量释放代码时可能会出现问题(您可能需要在循环中添加几个回合)。