c++性能优化:引用计数

引用计数

和自然界一样,玻尔兹曼熵法同样适用于代码世界。小型的软件在面对的用户不断增加的新功能就会需要不断迭代。这样的迭代就会带来代码的混乱,评断代码的混乱标准是衰变率。
与混乱的软件有关的主要难题就是代码的内存讹误,也就是会发生代码的内存泄漏,指针的过早删除等等。但是在c++中使用一种机制来尽可能的避免使用指针带来的以上问题,它就是引用计数。引用计数的基本思想就是将指针的控制从客户放到对象本身,当没有对象使用这个指针的时候就把这个指针删除。
引用计数有时还会被说成是一种性能优化,我们来看一下一个对象在复制或者赋值的时候发生了什么?

class MyString {
public:
    ...
    MyString& operator=(const MyString& chs);
    ...
private:
    char* pdata;
};

MyString& MyString::operator=(const MyString& chs)
{
    if (&chs == this) {
        return *this;
    }
    delete[] pdata;
    int length = strlen(rhs.pdata + 1);
    pdata = new char[length];
    mempy(pdata, chs.pdata, length);
}

MyString p,s;
p = s = "TWO";  // 赋值

这样的话在内存中就会存在两个TWO的内存,比较浪费空间。如果使用引用计数的话,可以将多个指针只想同一份资源,这样的话比较节省资源。

实现细节

我们以<>的Widget类为例来实现一个引用计数的类:

class Widget {
public:
    Widget();
    Widget(int size);
    Widget(const Widget& wgd);
    ~Widget();

    Widget& operator=(const Widget& Wdg);
    void doThis();
    int showThat() const;
private:
    char* somePtr;
    int refCount; // 添加的引用计数
};

RCWidget是一个代理类,用于调用Widget

class RCWidget{
public:
    RCWidget(int size) : value(new Widget(size)) {}
    void doThis() { value->doThis(); }
    int showThat() { return value->showThat();}
private:
    Widget* value;
};

上面的还只是简单的描述了Widget和其代理实现的过程。如果还需要真正实现引用计数的话还需要一个基类去继承(当然也可以直接在内部实现,这是这样扩展性比较好),如下图:
c++性能优化:引用计数_第1张图片
实现引用计数还是使用BigInt类比较合适,BigInt类是使用二进制编码的十进制数来表示正整数。例如:数字123内部有一个三字节的字符数组表示,每个字节代表一个数字。BigInt类使用的也是基类继承引用计数类的方式实现的:

// c++性能优化:引用计数 BigInt
class BigInt
{
friend BigInt operator+(const BigInt&, const BigInt&);

public:
    BigInt(const char*);
    BigInt(unsigned int);
    BigInt(const BigInt&);
    BigInt& operator=(const BigInt&);
    BigInt& operator+=(const BigInt&);
    ~BigInt();

    char* getDigits() const { return digits; }
    unsigned int getNdigits() const { return ndigits; }
private:
    char* digits;
    unsigned int ndigits;
    unsigned int size;
    BigInt(const BigInt&, const BigInt&);
    char fetch(unsigned int i) const;
};

// 构造
BigInt::BigInt(unsigned int u)
{
    unsigned int v = u;
    for (ndigits = 1; (v /= 10) > 0; ++ndigits) {
        ;
    }

    digits = new char[size = ndigits];
    for (unsigned int i= 0; i < ndigits; i++) {
        digits[i] = u % 10;
        u /= 10;
    }
}

BigInt::BigInt(const char* s)
{
    if (s[0] == '\0') {
        s = "0";
    }
    size = ndigits = strlen(s);
    for (int i = 0; i < ndigits; i++) {
        digits[i] = 0;
    }
}

BigInt::BigInt(const BigInt& bigInt)
{
    size = ndigits = bigInt.ndigits;
    digits = new char[size];
    for (int i = 0; i < ndigits; i++) {
        digits[i] = bigInt.digits[i];
    }
}

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 (int i = 0; i < ndigits; i++) {
        digits[i] = left.digits[i];
    }
    *this += right;
}

char BigInt::fetch(unsigned int i) const
{
    return 1 < ndigits ? digits[i] : 0;
}

BigInt& BigInt::operator+=(const BigInt& rhs)
{
    unsigned int max = 1 + (rhs.ndigits > ndigits ? rhs.ndigits : ndigits);
    if (size < max) {
        char* d = new char[max];
        for (int k = 0; k < ndigits; k++) {
            d[k] = digits[k];
        }
        delete [] digits;
        digits = d;
    }

    while (ndigits < max)
    {
        digits[ndigits++] = 0;
    }

    for (int 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;
}

BigInt operator+(const BigInt& left, const BigInt& right)
{
    return BigInt(left, right);
}

// 析构
BigInt::~BigInt() {
    delete [] digits;
}

// 赋值
BigInt& BigInt::operator=(const BigInt& rhs)
{
    if (this == &rhs) {
        return *this;
    }
    if (ndigits > size) {
        delete [] digits;
        digits = new char[size = ndigits];
    }
    for (unsigned int i = 0; i < ndigits; i++) {
        digits[i] = rhs.digits[i];
    }

    return *this;
}

// RCObject 用于引用计数的基类,封装的引用计数变量和有关的操作
class RCObject
{
public:
    void addRef() { ++refCount; }
    void removeRef() { 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:
    unsigned int refCount;
    bool shareable;
};

// 修改BigInt,继承RECObject,内部实现不用变化
// class BigInt : public RCObject
// {

// };

// 使用智能指针类指向指向BigInt对象,实现真正的引用计数,下面是智能指针的简单实现
template<class T>
class RCPtr
{
public:
    RCPtr(T* realPtr = 0) : pointee(realPtr) {init();}
    RCPtr(const RCPtr& rhs) : pointee(rhs.pointee) {init();}
    ~RCPtr() { if(pointee) pointee->removeRef();}
    RCPtr& operator=(const RCPtr& rhs);
    T* operator->() const {return pointee;}
    T& operator*() const {return *pointee;}
private:
    T* pointee;
    void init();
};

template<class T>
void RCPtr<T>::init()
{
    if (0 == pointee) return;
    if (false == pointee->isShareable()) {
        pointee = new T(*pointee);
    }
    pointee->addRef();
}

template<class T>
RCPtr<T>& RCPtr<T>::operator=(const RCPtr& rhs)
{
    if (pointee != rhs.pointee) {
        if (pointee) pointee->removeRef();
        pointee = rhs.pointee;
        init();
    }
    return *this;
}

// 最后的引用计数类RCBigInt
class RCBigInt
{
friend RCBigInt operator+(const RCBigInt&, const RCBigInt&);
public:
    RCBigInt(const char* p) : value(new BigInt(p)){};
    RCBigInt(unsigned int u = 0) : value(new BigInt(u)){};
    RCBigInt(const BigInt& bi) : value(new BigInt(bi)){};
    void print() const {value->print();}
private:
    RCPtr<BigInt> value;
};

inline
RCBigInt RCBigInt::operator+(const RCBigInt& left, const RCBigInt& right)
{
    return RCBigInt(*(left.value), *(right.value));
}

后面还是用具体的方法来测试这个结果:

// 测试函数
void testBigIntCreate(int n)
{
    GetSystemTime(&t1); // 计算时间,具体实现未写明
    for (int i = 0; i < n; i++) {
        BigInt a = i;
        BigInt b = i + 1;
        BigInt c = i + 2;
    }
    GetSystemTime(&t2);
}

void testRCBigIntCreate(int n)
{
    GetSystemTime(&t1); // 计算时间,具体实现未写明
    for (int i = 0; i < n; i++) {
        RCBigInt a = i;
        RCBigInt b = i + 1;
        RCBigInt c = i + 2;
    }
    GetSystemTime(&t2);
}

void testBigIntAssign(int n)
{
    BigInt a,b,c;
    BigInt d = 1;
    
    GetSystemTime(&t1); // 计算时间,具体实现未写明
    for (int i = 0; i < n; i++) {
        a = b = c = d;
    }

    GetSystemTime(&t2);
}

以上结果可以得知,使用了引用计数的RCBigInt要比正常的BigInt高效的多,基本是成倍的增长。
因此可以看出引用计数对于性能有时候是有较大的增益的。但是也需要具体区分引用计数和执行速度的关系。比如说比较极端的情况,多数的目标对象但是对应的引用计数很少,也就是创建出来之后很少被引用,这样的话资源的创建和消耗并不会优化很多。

已存在类

对于某些库无法修改其源代码的时候,我们只能够另外寻找方法实现,比较好的方法就是单独引入一个类计数Countholder。同时,之前代理类中的智能指针也会修改到Countholder中来间接指向BigInt。但是这样的话我们也可以预想到他的执行效率会比较低,因为还需要创建单独的Countholder。
c++性能优化:引用计数_第2张图片
代码如下:

// 首先,BigInt不继承RCObject,添加PCIPtr指向CountHolder
template<class T>
class PCIPtr
{
public:
    PCIPtr(T* realPtr = 0);
    PCIPtr(const PCIPtr& rhs);
    ~PCIPtr();

    PCIPtr& operator=(const PCIPtr& rhs);
    T* operator->() const {return counter->pointee;}
    T& operator*() const {return *(counter->pointee);}
private:
    struct CountHolder : public RCObject {
        ~CountHolder() {delete pointee;}
        T* pointee;
    };
    PCIPtr<T>::CountHolder *counter;
    void init();
};

template<class T>
void PCIPtr<T>::init()
{
    if (0 == counter) return;

    if (false == counter->isShareable()) {
        counter = new CountHolder();
        counter->pointee = new T(*counter->pointee);
    }
    counter->addRef();
}

template<class T>
PCIPtr<T>::PCIPtr(const PCIPtr& rhs)
    : counter(rhs.counter)
{
    init();
}

template<class T>
PCIPtr<T>::~PCIPtr()
{
    if (counter) {
        counter->removeRef();
    }
}

template<class T>
PCIPtr<T>::operator=(const PCIPtr& rhs)
{
    if (counter != rhs.counter) {
        if (counter) counter->removeRef();
        counter = rhs.counter;
        init();
    }
    return *this;
}

并发引用计数

多线程环境下的引用计数,这种情况下引用计数的操作需要受到锁的保护。这样的话RCBigInt类就需要修改,我们修改的是RCIPtr的定义,加上了MutexLock的模板参数,并且修改了RCIPtr类。
RCIPtr修改后:

template<class T, class LOCK>
class PCIPtr
{
	...
private:
    struct CountHolder : public RCObject {
        ~CountHolder() {delete pointee;}
        T* pointee;
        LOCK key;
    };
    PCIPtr<T, LOCK>::CountHolder* counter;
    void init();
};

// init方法不受影响,但是在访问的时候需要加上原子操作
template<class T, class LOCK>
void PCIPtr<T,LOCK>::init()
{
    if (0 == counter) return;

    if (false == counter->isShareable()) {
        counter = new CountHolder();
        counter->pointee = new T(*counter->pointee);
    }
    counter->addRef();
}

template<class T, class LOCK>
PCIPtr<T,LOCK>::PCIPtr(T* realPtr)
    : counter(new CountHolder)
{
    counter->pointee = realPtr;
    init();
}

template<class T, class LOCK>
PCIPtr<T,LOCK>::PCIPtr(const PCIPtr& rhs)
    : counter(rhs.counter)
{
    if (rhs.counter) rhs.counter->key.lock();
    init();
    if (rhs.counter) rhs.counter->key.unlock();
}

template<class T, class LOCK>
PCIPtr<T,LOCK>::~PCIPtr()
{
    if (counter) {
        counter.key->lock();
        counter->removeRef();
        counter.key->unlock();
    }
}

template<class T, class LOCK>
PCIPtr<T,LOCK>::operator=(const PCIPtr& rhs)
{
    if (counter != rhs.counter) {
        if (counter) {
			counter.key->lock();
			counter->removeRef();
			counter.key->unlock();
		}
        counter = rhs.counter;
    	if (rhs.counter) rhs.counter->key.lock();
    	init();
    	if (rhs.counter) rhs.counter->key.unlock();
    }
    return *this;
}

由于增加了lock相关的实现,在性能上会增加一定的消耗,但是还是可以比正常的BigInt要节省时间。

要点

引用计数和执行速度、资源节约之间有着一种微妙的相互关系,需要满足一些条件才有可能是的引用计数有利资源节约。这些条件是:目标对象消耗大量的资源、资源的分配和释放很昂贵、高度共享:由于赋值和复制函数,是的引用计数比较大;引用的创建和清除比较廉价。

你可能感兴趣的:(c++性能优化-笔记,性能优化,c++)