引用计数(reference counting):基本思想是将销毁对象的职责从客户端代码转移到对象本身。对象跟踪记录自身当前被引用的数目,在引用计数达到零时自行销毁。换句话说,对象不再被使用时自行销毁。
引用计数和执行速度之间的关系是与上下文紧密关联的。该关系取决于以下几个因素:
(1). 目标对象的资源消耗量集中于哪些方面?如果目标对象使用过多内存,比如未保护内存,将使可用内存受限,并导致显著的性能损失,表现为缓存命不中和页面错误。
(2). 分配(释放)目标对象所使用资源的代价有多高?即便从堆中分配1字节空间也需要执行数百条指令。释放时亦然。
(3). 可能有多少对象共享目标对象的单个实例?通过使用赋值操作符和拷贝构造函数可以提升共享率。
(4). 创建(销毁)第一个(最后一个)目标对象引用的额度有多高?使用构造函数而不是拷贝构造函数创建新的引用计数对象时,会产生对目标对象的初次引用。这种操作代价高昂。与创建一个新的目标对象相比,这种方法包含更多开销。移除最后一次引用时也是如此。
对于预先存在且无法修改的类,引用计数依然可以实现,只不过,这种情况需要对设计进行一些修改。也可以在多线程执行环境下处理引用计数。在这种情况下,多个线程可能会并发访问同一个引用计数对象。因此必须对维持引用计数的变量进行保护,使其对进行的更新是原子操作。原子更新操作要求有一个锁定机制。
引用计数在性能上并非无往不胜。引用计数、执行时间和资源维护会产生微妙的相互作用,如果对性能方面的考虑很重要,就必须对这几个方面仔细进行评估。引用计数是提升还是损害性能取决于其使用方式。下面的任意一种情况都可以使引用计数变得更为有效:目标对象是很大的资源消费者;资源分配和释放的代价很高;高度共享,由于使用赋值操作符和拷贝构造函数,引用计数的性能可能会很高;创建和销毁引用的代价低廉。反之,则应跳出引用计数而转为使用更加有效的简单非计数对象。
以下是测试代码(reference_counting.cpp):
#include "reference_counting.hpp"
#include
#include
#include
#include
namespace reference_counting_ {
// reference: 《提高C++性能的编程技术》:第十二章:引用计数
namespace {
// 为使引用计数更加方便,我们需要为每个BigInt对象关联一个引用计数。
// 实现这一操作的方式有两种,为BigInt直接添加refCount成员或使其继承自基类RCObject
// RCObject是引用计数对象的基类,并且封装了所有引用计数变量以及针对这些变量的操作
class RCObject {
public:
void addReference() { ++refCount; }
void removeReference() { if (--refCount == 0) delete this; }
void markUnshareable() { shareable = false; }
bool isShareable() const { return shareable; }
bool isShared() const { return refCount > 1; }
protected:
RCObject() : refCount(0), shareable(true) {}
RCObject(const RCObject& rhs) : refCount(0), shareable(true) {}
RCObject& operator=(const RCObject& rhs) { return *this; }
virtual ~RCObject() {}
private:
int refCount;
bool shareable;
};
// BigInt类的功能是将正整数表示成用二进制编码的十进制形式
class BigInt : public RCObject {
friend BigInt operator+ (const BigInt&, const BigInt&);
public:
BigInt(const char*);
BigInt(unsigned = 0);
BigInt(const BigInt&);
BigInt& operator= (const BigInt&);
BigInt& operator+= (const BigInt&);
~BigInt();
char* getDigits() const { return digits; }
unsigned getNdigits() const { return ndigits; }
void print() { fprintf(stdout, "digits: %s\n", digits); }
private:
char* digits;
unsigned ndigits;
unsigned size; // 分配的字符串的大小
BigInt(const BigInt&, const BigInt&);
char fetch(unsigned i) const;
};
BigInt::BigInt(unsigned u)
{
unsigned v = u;
for (ndigits = 1; (v/=10) > 0; ++ndigits) {
;
}
digits = new char[size=ndigits];
for (unsigned i = 0; i < ndigits; ++i) {
digits[i] = u%10;
u /= 10;
}
}
BigInt::~BigInt()
{
delete [] digits;
}
BigInt& BigInt::operator=(const BigInt& rhs)
{
if (this == &rhs) return *this;
ndigits = rhs.ndigits;
if (ndigits > size) {
delete [] digits;
digits = new char[size = ndigits];
}
for (unsigned i = 0; i < ndigits; ++i) {
digits[i] = rhs.digits[i];
}
return *this;
}
BigInt::BigInt(const char* s)
{
if (s[0] == '\0') {
s = "0";
}
size = ndigits = strlen(s);
digits = new char[size];
for (unsigned i = 0; i < ndigits; ++i) {
digits[i] = s[ndigits-1-i] - '0';
}
}
BigInt::BigInt(const BigInt& copyFrom)
{
size = ndigits = copyFrom.ndigits;
digits = new char[size];
for (unsigned i = 0; i < ndigits; ++i) {
digits[i] = copyFrom.digits[i];
}
}
// BigInt = left + right
BigInt::BigInt(const BigInt& left, const BigInt& right)
{
size = 1 + (left.ndigits > right.ndigits ? left.ndigits : right.ndigits);
digits = new char[size];
ndigits = left.ndigits;
for (unsigned i = 0; i < ndigits; ++i) {
digits[i] = left.digits[i];
}
*this += right;
}
inline char BigInt::fetch(unsigned i) const
{
return i < ndigits ? digits[i] : 0;
}
BigInt& BigInt::operator+=(const BigInt& rhs)
{
unsigned max = 1 + (rhs.ndigits > ndigits ? rhs.ndigits : ndigits);
if (size < max) {
char* d = new char[size=max];
for (unsigned i = 0; i < ndigits; ++i) {
d[i] = digits[i];
}
delete [] digits;
digits = d;
}
while (ndigits < max) {
digits[ndigits++] = 0;
}
for (unsigned i = 0; i < ndigits; ++i) {
digits[i] += rhs.fetch(i);
if (digits[i] >= 10) {
digits[i] -= 10;
digits[i+1] = 1;
}
}
if (digits[ndigits-1] == 0) {
--ndigits;
}
return *this;
}
std::ostream& operator<<(std::ostream& os, BigInt& bi)
{
char c;
const char* d = bi.getDigits();
for (int i = bi.getNdigits() - 1; i >= 0; i--) {
c = d[i] + '0';
os << c;
}
os << std::endl;
return os;
}
inline BigInt operator+(const BigInt& left, const BigInt& right)
{
return BigInt(left, right);
}
// 智能指针:这种特殊指针的任务是对引用计数进行登记
template
class RCPtr {
public:
RCPtr(T* realPtr = 0) : pointee(realPtr) { init(); }
RCPtr(const RCPtr& rhs) : pointee(rhs.pointee) { init(); }
~RCPtr() { if (pointee) pointee->removeReference(); }
RCPtr& operator=(const RCPtr& rhs);
T* operator->() const { return pointee; }
T& operator*() const { return *pointee; }
private:
T* pointee;
void init();
};
template
void RCPtr::init()
{
if (0 == pointee) return;
if (false == pointee->isShareable()) {
pointee = new T(*pointee);
}
pointee->addReference();
}
template
RCPtr& RCPtr::operator=(const RCPtr& rhs)
{
if (pointee != rhs.pointee) {
if (pointee) pointee->removeReference();
pointee = rhs.pointee;
init();
}
return *this;
}
// RCBigInt:构造BigInt的引用计数实现,RCBigInt需要指向一个真正的BigInt对象
// 如果是频繁赋值和复制RCBigInt对象的工作,RCBigInt会大显身手。另外,与直接使用简单BigInt相比,
// 由于创建了对新BigInt对象的初次引用,使用新RCBigInt对象的代价要更高一些。每当RCBigInt产生了
// 对BigInt的初次引用时,就会在堆内存中创建一个BigInt对象并指向它。对于基于堆栈(局部变量)的简单
// BigInt对象而言,则不必付出这种代价。此类的情况在移除最后一次BigInt引用时也会发生。这是因为
// 底层的BigInt对象被释放并还给堆内存。
class RCBigInt {
friend RCBigInt operator+(const RCBigInt&, const RCBigInt&);
public:
RCBigInt(const char* p) : value(new BigInt(p)) {}
RCBigInt(unsigned u = 0) : value(new BigInt(u)) {}
RCBigInt(const BigInt& bi) : value(new BigInt(bi)) {}
void print() const { value->print(); }
private:
RCPtr value;
};
inline RCBigInt operator+(const RCBigInt& left, const RCBigInt& right)
{
return RCBigInt(*(left.value) + *(right.value));
}
} // namespace
int test_reference_counting_1()
{
std::chrono::high_resolution_clock::time_point time_start, time_end;
const int count{10000000};
{ // test BigInt create
time_start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < count; ++i) {
BigInt a = i;
BigInt b = i + 1;
BigInt c = i + 2;
BigInt d = i + 3;
}
time_end = std::chrono::high_resolution_clock::now();
fprintf(stdout, "BigInt create time spend: %f seconds\n",
(std::chrono::duration_cast>(time_end-time_start)).count());
}
{ // test RCBigInt create
// RCBigInt测试会更多地忙于初次引用的创建及之后将其销毁的工作
time_start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < count; ++i) {
RCBigInt a = i;
RCBigInt b = i + 1;
RCBigInt c = i + 2;
RCBigInt d = i + 3;
}
time_end = std::chrono::high_resolution_clock::now();
fprintf(stdout, "RCBigInt create time spend: %f seconds\n",
(std::chrono::duration_cast>(time_end-time_start)).count());
}
{ // test BigInt assign
BigInt a, b, c;
BigInt d = 1;
time_start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < count; ++i) {
a = b = c = d;
}
time_end = std::chrono::high_resolution_clock::now();
fprintf(stdout, "BigInt assign time spend: %f seconds\n",
(std::chrono::duration_cast>(time_end-time_start)).count());
}
{ // test RCBigInt assign
// 对RCBigInt对象的赋值操作效率高于BigInt对象,但是创建和销毁对象时却相反
RCBigInt a, b, c;
RCBigInt d = 1;
time_start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < count; ++i) {
a = b = c = d;
}
time_end = std::chrono::high_resolution_clock::now();
fprintf(stdout, "RCBigInt assign time spend: %f seconds\n",
(std::chrono::duration_cast>(time_end-time_start)).count());
}
return 0;
}
} // namespace reference_counting_
执行结果如下:
GitHub: https://github.com/fengbingchun/Messy_Test