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

引用计数和执行速度之间的关系是与上下文紧密关联的。该关系取决于以下几个因素:

(1). 目标对象的资源消耗量集中于哪些方面?如果目标对象使用过多内存,比如未保护内存,将使可用内存受限,并导致显著的性能损失,表现为缓存命不中和页面错误。

(2). 分配(释放)目标对象所使用资源的代价有多高?即便从堆中分配1字节空间也需要执行数百条指令。释放时亦然。

(3). 可能有多少对象共享目标对象的单个实例?通过使用赋值操作符和拷贝构造函数可以提升共享率。

(4). 创建(销毁)第一个(最后一个)目标对象引用的额度有多高?使用构造函数而不是拷贝构造函数创建新的引用计数对象时,会产生对目标对象的初次引用。这种操作代价高昂。与创建一个新的目标对象相比,这种方法包含更多开销。移除最后一次引用时也是如此。

对于预先存在且无法修改的类,引用计数依然可以实现,只不过,这种情况需要对设计进行一些修改。也可以在多线程执行环境下处理引用计数。在这种情况下,多个线程可能会并发访问同一个引用计数对象。因此必须对维持引用计数的变量进行保护,使其对进行的更新是原子操作。原子更新操作要求有一个锁定机制。

引用计数在性能上并非无往不胜。引用计数、执行时间和资源维护会产生微妙的相互作用,如果对性能方面的考虑很重要,就必须对这几个方面仔细进行评估。引用计数是提升还是损害性能取决于其使用方式。下面的任意一种情况都可以使引用计数变得更为有效:目标对象是很大的资源消费者;资源分配和释放的代价很高;高度共享,由于使用赋值操作符和拷贝构造函数,引用计数的性能可能会很高;创建和销毁引用的代价低廉。反之,则应跳出引用计数而转为使用更加有效的简单非计数对象。

以下是测试代码(reference_counting.cpp):

#include "reference_counting.hpp"
#include <iostream>
#include <chrono>
#include <string>
#include <string.h>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 T>
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<class T>
void RCPtr<T>::init()
{if (0 == pointee) return;if (false == pointee->isShareable()) {pointee = new T(*pointee);}pointee->addReference();
}template<class T>
RCPtr<T>& RCPtr<T>::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<BigInt> value;
};inline RCBigInt operator+(const RCBigInt& left, const RCBigInt& right)
{return RCBigInt(*(left.value) + *(right.value));
}} // namespaceint test_reference_counting_1()
{std::chrono::high_resolution_clock::time_point time_start, time_end;const int count{10000000};{ // test BigInt createtime_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<std::chrono::duration<double>>(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<std::chrono::duration<double>>(time_end-time_start)).count());
}{ // test BigInt assignBigInt 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<std::chrono::duration<double>>(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<std::chrono::duration<double>>(time_end-time_start)).count());
}return 0;
}} // namespace reference_counting_

执行结果如下:

GitHub: https://github.com/fengbingchun/Messy_Test

提高C++性能的编程技术笔记:引用计数+测试代码相关推荐

  1. 提高C++性能的编程技术笔记:总结

    <提高C++性能的编程技术>这本书是2011年出版的,书中有些内容的介绍可能已经过时,已不再适用于现在的C++编程中,但大部分内容还是很有参考意义的. 这里是基于之前所有笔记的简单总结,笔 ...

  2. 提高C++性能的编程技术笔记:内联+测试代码

    内联类似于宏,在调用方法内部展开被调用方法,以此来代替方法的调用.一般来说表达内联意图的方式有两种:一种是在定义方法时添加内联保留字的前缀:另一种是在类的头部声明中定义方法. 虽然内联方法的调用方式和 ...

  3. 提高C++性能的编程技术笔记:临时对象+测试代码

    类型不匹配:一般情况是指当需要X类型的对象时提供的却是其它类型的对象.编译器需要以某种方式将提供的类型转换成要求的X类型.这一过程可能会产生临时对象. 按值传递:创建和销毁临时对象的代价是比较高的.倘 ...

  4. 提高C++性能的编程技术笔记:设计优化/可扩展性/系统体系结构相关+测试代码

    1. 设计优化 我们可以粗略地将性能优化分为两种类型:编码优化和设计优化.编码优化定义为不需要完整理解要解决的问题或者应用程序的执行流程就能实施的优化.通过定义看出,编码优化用于局部代码,同时该过程不 ...

  5. 提高C++性能的编程技术笔记:编码优化+测试代码

    缓存:在现代处理器中,缓存经常与处理器中的数据缓存和指令缓存联系在一起.缓存主要用来存储使用频繁而且代价高昂的计算结果,这样就可以避免对这些结果的重复计算.如,循环内对常量表达式求值是一种常见的低性能 ...

  6. 提高C++性能的编程技术笔记:标准模板库+测试代码

    标准模板库(Standard Template Library, STL)是容器和通用算法的强效组合. 渐近复杂度:算法的渐近复杂度是对算法性能的近似估计.它是算法集到特定性能标准集的映射.如果需要对 ...

  7. 提高C++性能的编程技术笔记:多线程内存池+测试代码

    为了使多个线程并发地分配和释放内存,必须在分配器方法中添加互斥锁. 全局内存管理器(通过new()和delete()实现)是通用的,因此它的开销也非常大. 因为单线程内存管理器要比多线程内存管理器快的 ...

  8. 提高C++性能的编程技术笔记:单线程内存池+测试代码

    频繁地分配和回收内存会严重地降低程序的性能.性能降低的原因在于默认的内存管理是通用的.应用程序可能会以某种特定的方式使用内存,并且为不需要的功能付出性能上的代价.通过开发专用的内存管理器可以解决这个问 ...

  9. 提高C++性能的编程技术笔记:虚函数、返回值优化+测试代码

    虚函数:在以下几个方面,虚函数可能会造成性能损失:构造函数必须初始化vptr(虚函数表):虚函数是通过指针间接调用的,所以必须先得到指向虚函数表的指针,然后再获得正确的函数偏移量:内联是在编译时决定的 ...

最新文章

  1. form表单提交前进行ajax或js验证,校验不通过不提交
  2. VBScript中InStr函数的用法
  3. COM:追踪植物-微生物组互作的进化历史
  4. Discuz! X3.2新增管理员无法登录后台的解决办法
  5. 拿到大厂产品经理offer的应届生都是什么水平?
  6. jquery和php怎么链接地址,jQuery操作url地址(附代码)
  7. sensor曝光量和曝光行的区别_拼多多新手商家怎样快速提高曝光量?
  8. nodejs常用模块-url
  9. 2017软件工程实践总结
  10. QQ浏览器如何更改截图路径
  11. 帆软与中国环境科学院合作,共同推进数字化管理建设
  12. 网站性能优化 - 数据库及服务器架构篇
  13. 算法笔记_138:稳定婚姻问题(Java)
  14. 机器学习 之 LBP特征
  15. VirtualBox硬盘扩容
  16. 区块链会员积分系统如何构建及应用逻辑
  17. Java 8 reduce 是什么
  18. APS计划排程系统和生产排产系统,需要哪些基础资料(一)
  19. 微信小程序支付签名生成(客户端)
  20. 忠魁互联网站SEO优化的外链建设方法

热门文章

  1. docker 镜像容器导入导出、查看日志、拷贝文件命令
  2. OpenCV中的二进制鲁棒独立基本特征——BRIEF
  3. GO Negotiation流程分析
  4. LeetCode刷题记录4——67. Add Binary(easy)
  5. mysql 5.6.24 win32_mysql-5.6.24-win32解决没有my.ini并且修改编码
  6. QString与string的相互转换
  7. Node.js开发WEB项目后端接口API,基于mysql5.7数据库(小试牛刀)
  8. UE中的自动化学习课程
  9. WCDMA中的URA和LA/RA
  10. 因链接静态库先后顺序不正确,引起符号定义找不到