我想知道引用计数shared_ptr
的性能问题,所以我编写了一个愚蠢的程序,它以非常低效的方式计算pow(2, n)
,具有数百万个内存分配和解除分配的顺序测试shared_ptr
,而不是让编译器优化所有东西,即使是完全优化。
编译,
g++ t.cpp -std=c++11 -ot -DV_ -pedantic -Ofast -s -fwhole-program
其中-DV_表示使用std::vector
作为容器。
Ran as,
valgrind ./t 10000
表示计算pow(2, 10000)
表示我,
total heap usage: 45,174,051 allocs, 45,174,051 frees, 783,160,061 bytes allocated
和
time ./t 10000
输出
real 0m1.698s
user 0m1.693s
sys 0m0.004s
考虑到完成堆分配的数量,这是非常快的。该系统是带有i5 CPU和Ubuntu Linux操作系统的笔记本电脑。
我在这里使用的基本模式是定义将shared_ptr
扩展到其原始类的容器类。
#if defined(V_)
#define C_ std::vector
#elif defined(L_)
#define C_ std::list
#elif defined(D_)
#define C_ std::deque
#else
#error
#endif
template<typename T>
class Container : public std::shared_ptr<C_<T>>
{
public:
Container():
std::shared_ptr<C_<T>>(new C_<T>)
{
}
};
class String : public std::shared_ptr<std::string>
{
public:
String():
std::shared_ptr<std::string>(new std::string)
{
}
String(const char* s):
std::shared_ptr<std::string>(new std::string(s))
{
}
const char* getCString()
{
return get()->c_str();
}
};
这样我根本不用担心是通过值传递还是通过引用,const引用,右值引用等等。按价值传递一切工作正常并且非常便宜。它应该只是指针复制和引用计数更新。我可以根据需要添加一些深层复制方法,通过这些方法,我还可以轻松查看程序中正在发生的深层复制。仅仅因为错过参考标记而使整个矢量被复制是不会发生的。我还要说大型物品的深拷贝是必要的情况并不常见。
嗯,好处是显而易见的。换句话说,Java或C#将提供比C ++更好的生产力。但必须有性能损失。在大多数用户应用程序中,这种损失实际上并不算什么,但我喜欢编写游戏,它必须在30ms左右完成每帧的计算。我真的不能用真正的垃圾收集语言编写游戏,例如Java,因为我确实看到游戏意外暂停了一段很短但很明显的时刻并且恢复了。感觉不对劲。但是通过引用计数(伪)垃圾收集,资源被声明或释放的点非常有保障。
所以我正在考虑从最基础开始一个使用这种模式的项目,但是我没有太多使用引用计数指针进行足够大项目的经验。我真的处于试验阶段。但是你会说在时间关键程序中使用shared_ptr
基本上不是一个好主意,并且没有人知道在项目变得足够大之后其影响是什么,直到几乎不可能改变任何东西内心深处,随处可见。 (我实际上担心这部分)。或者我只是过分低估了现代计算机的伟大之处?
你有处理智能指针的好坏经验吗?这些可以在多线程中正常工作吗?编译器可以对它们进行多少优化?现代硬件是否能很好地处理参考计数操作?有关引用计数指针的任何其他有用的事实?
如果您有兴趣,可以在下面看到整个测试程序。
#include <cstdlib>
#include <cmath>
#include <iostream>
#include <vector>
#include <list>
#include <deque>
#include <queue>
#include <memory>
#if defined(V_)
#define C_ std::vector
#elif defined(L_)
#define C_ std::list
#elif defined(D_)
#define C_ std::deque
#else
#error
#endif
void check(bool isOkay)
{
if (!isOkay)
{
std::exit(EXIT_FAILURE);
}
}
template<typename T>
class Container : public std::shared_ptr<C_<T>>
{
public:
Container():
std::shared_ptr<C_<T>>(new C_<T>)
{
}
};
class String : public std::shared_ptr<std::string>
{
public:
String():
std::shared_ptr<std::string>(new std::string)
{
}
String(const char* s):
std::shared_ptr<std::string>(new std::string(s))
{
}
const char* getCString() const
{
return get()->c_str();
}
};
class SlowInteger : public Container<int>
{
public:
SlowInteger()
{
}
SlowInteger(int n)
{
check(0 <= n && n < 20);
if (n < 10)
{
get()->push_back(n);
}
else
{
get()->push_back(n - 10);
get()->push_back(1);
}
}
int toInt() const
{
if (get()->size() == 1)
{
return get()->front();
}
else
{
return get()->front() + 10 * get()->back();
}
}
};
class VerySlowInteger : public Container<SlowInteger>
{
public:
VerySlowInteger(String stringNumber)
{
for (auto it = stringNumber->rbegin(); it != stringNumber->rend(); ++it)
{
get()->push_back(SlowInteger(*it - '0'));
}
}
void x2()
{
int m = 0;
for (auto it = get()->begin(); it != get()->end(); ++it)
{
int n = it->toInt() * 2;
if (n < 10)
{
*it = SlowInteger(m + n);
m = 0;
}
else
{
*it = SlowInteger(n - 10 + m);
m = 1;
}
}
if (m == 1)
{
get()->push_back(SlowInteger(1));
}
}
void x2n(int n)
{
check(n > 0);
for (int i = 0; i < n; ++i)
{
x2();
}
}
String toString()
{
String s;
for (auto it = get()->rbegin(); it != get()->rend(); ++it)
{
s->push_back(it->toInt() + '0');
}
return s;
}
};
int main(int argc, char **argv)
{
check(argc == 2);
int n = std::atoi(argv[1]);
check(n > 0);
VerySlowInteger nSlow("1");
nSlow.x2n(n);
String s = nSlow.toString();
std::cout << s.getCString() << std::endl;
return EXIT_SUCCESS;
}