引用计数

和自然界一样,玻尔兹曼熵法同样适用于代码世界。小型的软件在面对的用户不断增加的新功能就会需要不断迭代。这样的迭代就会带来代码的混乱,评断代码的混乱标准是衰变率。
与混乱的软件有关的主要难题就是代码的内存讹误,也就是会发生代码的内存泄漏,指针的过早删除等等。但是在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的内存,比较浪费空间。如果使用引用计数的话,可以将多个指针只想同一份资源,这样的话比较节省资源。

实现细节

我们以<<More Effective C++>>的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和其代理实现的过程。如果还需要真正实现引用计数的话还需要一个基类去继承(当然也可以直接在内部实现,这是这样扩展性比较好),如下图:

实现引用计数还是使用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。

代码如下:

// 首先,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++性能优化:引用计数相关推荐

  1. 提高C++性能的编程技术笔记:引用计数+测试代码

    引用计数(reference counting):基本思想是将销毁对象的职责从客户端代码转移到对象本身.对象跟踪记录自身当前被引用的数目,在引用计数达到零时自行销毁.换句话说,对象不再被使用时自行销毁 ...

  2. 【Android 内存优化】Java 内存模型 ( Java 虚拟机内存模型 | 线程私有区 | 共享数据区 | 内存回收算法 | 引用计数 | 可达性分析 )

    文章目录 一. Java 虚拟机内存模型 二. 程序计数器 ( 线程私有区 ) 三. 虚拟机栈 ( 线程私有区 ) 四. 本地方法栈 ( 线程私有区 ) 五. 方法区 ( 共享数据区 ) 1. 方法区 ...

  3. ios 常见性能优化

    1. 用ARC管理内存 2. 在正确的地方使用reuseIdentifier 3. 尽可能使Views透明 4. 避免庞大的XIB 5. 不要block主线程 6. 在Image Views中调整图片 ...

  4. Python编程规范及性能优化

    为什么80%的码农都做不了架构师?>>>    Ptyhon编程规范 编码 所有的 Python 脚本文件都应在文件头标上 # -*- coding:utf-8 -*- .设置编辑器 ...

  5. Java引用计数与实现

    引用计数(Reference Counting)可作为内存管理办法,也是老代jvm垃圾回收策略之一,原理简单但是仍有广泛的引用,如OkHttp,netty等. 回收原理 对象在创建实例的时候会在堆内存 ...

  6. 《C++应用程序性能优化::第五章动态内存管理》学习和理解

    <C++应用程序性能优化::第五章动态内存管理>学习和理解 说明:<C++应用程序性能优化> 作者:冯宏华等 2007年版. 2010.8.29 cs_wuyg@126.com ...

  7. Oracle数据库管理----性能优化

    https://blog.csdn.net/yzllz001/article/details/54848513 数据库访问优化法则 要正确的优化SQL,我们需要快速定位能性的瓶颈点,也就是说快速找到我 ...

  8. JVM性能优化, Part 2 ―― 编译器

    2019独角兽企业重金招聘Python工程师标准>>> ImportNew注:本文是JVM性能优化 – 第2篇 <JVM性能优化, Part 2 ―― 编译器>第一篇 & ...

  9. iOS的一些常用性能优化,和内存优化的方法

    也是借鉴别人的,感兴趣的可以仔细看看哈 1. 用ARC管理内存 ARC(Automatic ReferenceCounting, 自动引用计数)和iOS5一起发布,它避免了最常见的也就是经常是由于我们 ...

最新文章

  1. 使用文本用户界面(NMTUI)进行网络配置
  2. mysql 白皮书_mysql企业版 《 MySQL企业版中文白皮书 》.cn.doc
  3. C++11 std::bind 和 std::placeholder
  4. 计算机二级语义网络的研究现状与展望,计算机二级access选择题题库研究.doc
  5. 伍迷随想冷饭集 之 瞻前顾后之随想
  6. 为什么现在好多年轻人连1万都掏不出来,却觉得100万很少?
  7. 电脑很卡反应很慢该如何处理_我的苹果电脑中毒了,好开心
  8. mysql c api简单连接池
  9. scala 提取器模式匹配_Scala提取器应用,取消应用和模式匹配
  10. 【python、pyqt5】,打包出现的若干问题
  11. sql学生选课管理系统
  12. 盘点一下 在Python中安装包的三种方法
  13. 【SQLite】C++链接SQLite读数据乱码问题(非中文)
  14. CNTV CBOX的服务项
  15. labview—电子表格文件读写
  16. python turtle 绘制北京天安门
  17. 商汤科技——机器视觉面试
  18. 【学术】如何长时间高效学习
  19. 广告拦截软件测试简历,ADSafe广告拦截效果测试
  20. Java8新特性之Joining

热门文章

  1. Java中线程的状态
  2. 嵌入式linux的u-boot系统启动过程,嵌入式linux操作系统u-boot启动顺序以及代码解析...
  3. 虚拟机启动Ubuntu 出现 systemd-journald[404] 系统进入了initramfs 终端
  4. (13.1.1)PMBOK之一(附):组织系统及其影响,过程资产环境因素与项目经理
  5. python语音识别播放音乐_Python人工智能 : PyAudio 实现录音 自动化交互实现问答
  6. 机器学习中的AUC是什么
  7. 10家共享单车平台押金情况实测 承诺“随时退回”不如免押金
  8. Linux系统多网卡绑定各配置模式详解
  9. kvm连接服务器显示不全有重影,KVM的使用中最常见的故障排除与处理
  10. editplus快捷键汇总