我经常读到,在大多数情况下,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):
区别来自哪里?它可以解释吗?
答案 0 :(得分:19)
于2014年1月1日更新
我知道这个问题很老,但结果仍然适用于G ++ 4.7.0和libstdc ++ 4.7。所以,我试图找出原因。
您在此处进行基准测试的是使用 -O0 的解除引用效果,并查看unique_ptr
和shared_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左右的任何测量都是可疑的。在测量释放代码时可能会出现问题(您可能需要在循环中添加几个回合)。