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

2023-11-09

引用计数(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));
}

} // 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<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 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<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 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

  • C++中的虚函数表介绍

    在C 语言中 当我们使用基类的引用或指针调用一个虚成员函数时会执行动态绑定 因为我们直到运行时才能知道到底调用了哪个版本的虚函数 所以所有虚函数都必须有定义 通常情况下 如果我们不使用某个函数 则无须为该函数提供定义 但是我们必须为每一个虚
  • C和C++安全编码笔记:整数安全

    5 1 整数安全导论 整数由包括0的自然数 0 1 2 3 和非零自然数的负数 1 2 3 构成 5 2 整数数据类型 整数类型提供了整数数学集合的一个有限子集的模型 一个具有整数类型的对象的值是附着在这个对象上的数学值 一个具有整数类型的
  • C++/C++11中头文件algorithm的使用

  • C++11中std::future的使用

    C 11中的std future是一个模板类 std future提供了一种用于访问异步操作结果的机制 std future所引用的共享状态不能与任何其它异步返回的对象共享 与std shared future相反 std future r
  • C和C++安全编码笔记:指针诡计

    指针诡计 pointer subterfuge 是通过修改指针值来利用程序漏洞的方法的统称 可以通过覆盖函数指针将程序的控制权转移到攻击者提供的外壳代码 shellcode 当程序通过函数指针执行一个函数调用时 攻击者提供的代码将会取代原本
  • 概率论中高斯分布(正态分布)介绍及C++11中std::normal_distribution的使用

    高斯分布 最常用的分布是正态分布 normal distribution 也称为高斯分布 Gaussian distribution 正态分布N x 2 呈现经典的 钟形曲线 的形状 其中中心峰的x坐标由 给出 峰的宽度受 控制 正态分布由
  • C++11中头文件atomic的使用

    原子库为细粒度的原子操作提供组件 允许无锁并发编程 涉及同一对象的每个原子操作 相对于任何其他原子操作是不可分的 原子对象不具有数据竞争 data race 原子类型对象的主要特点就是从不同线程访问不会导致数据竞争 因此从不同线程访问某个原
  • C和C++安全编码笔记:文件I/O

    C和C 程序通常会对文件进行读写 并将此作为它们正常操作的一部分 不计其数的漏洞正是由这些程序与文件系统 其操作由底层操作系统定义 交互方式的不规则性而产生的 这些漏洞最常由文件的识别问题 特权管理不善 以及竞争条件导致 8 1 文件I O
  • log库spdlog简介及使用

    spdlog是一个开源的 快速的 仅有头文件的C 11 日志库 code地址在 https github com gabime spdlog 目前最新的发布版本为0 14 0 它提供了向流 标准输出 文件 系统日志 调试器等目标输出日志的能
  • Effective C++改善程序与设计的55个具体做法笔记

    Scott Meyers大师Effective三部曲 Effective C More Effective C Effective STL 这三本书出版已很多年 后来又出版了Effective Modern C More Effective
  • C语言中signal函数简介及使用

    signal h是C标准函数库中的信号处理部分 定义了程序执行时如何处理不同的信号 信号用作进程间通信 报告异常行为 如除零 用户的一些按键组合 如同时按下Ctrl与C键 产生信号SIGINT C 中的对应头文件是csignal C语言标准
  • 提高C++性能的编程技术笔记:引用计数+测试代码

    引用计数 reference counting 基本思想是将销毁对象的职责从客户端代码转移到对象本身 对象跟踪记录自身当前被引用的数目 在引用计数达到零时自行销毁 换句话说 对象不再被使用时自行销毁 引用计数和执行速度之间的关系是与上下文紧
  • 程序员的自我修养--链接、装载与库笔记:Linux共享库的组织

    共享库 Shared Library 概念 其实从文件结构上来讲 共享库和共享对象没什么区别 Linux下的共享库就是普通的ELF共享对象 由于共享对象可以被各个程序之间共享 所以它也就成为了库的很好的存在形式 很多库的开发者都以共享对象的
  • C++11中std::bind的使用

    std bind函数是用来绑定函数调用的某些参数的 std bind它可以预先把指定可调用实体的某些参数绑定到已有的变量 产生一个新的可调用实体 它绑定的参数的个数不受限制 绑定的具体哪些参数也不受限制 由用户指定 std bind 1 将
  • C++14中返回类型推导的使用

    使用C 14中的auto返回类型 编译器将尝试自动推导 deduce 返回类型 namespace int xx 1 auto f return xx return type is int const auto f3 return xx r
  • 概率论中伯努利分布(bernoulli distribution)介绍及C++11中std::bernoulli_distribution的使用

    Bernoulli分布 Bernoulli distribution 是单个二值随机变量的分布 它由单个参数 0 1 给出了随机变量等于1的概率 它具有如下的一些性质 P x 1 P x 0 1 P x x x 1 1 x Ex x Var
  • C++/C++11中头文件iterator的使用

  • C++11中std::shared_future的使用

    C 11中的std shared future是个模板类 与std future类似 std shared future提供了一种访问异步操作结果的机制 不同于std future std shared future允许多个线程等待同一个共
  • C++14中binary literals的使用

    一个形如42的值被称作字面值常量 literal 这样的值一望而知 每个字面值常量都对应一种数据类型 字面值常量的形式和值决定了它的数据类型 我们可以将整型字面值写作十进制 基数为10 八进制 基数为8 或十六进制 基数为16 数的形式 以
  • C++/C++11中变长参数的使用

    C C 11中的变长参数可以应用在宏 函数 模板中 1 宏 在C99标准中 程序员可以使用变长参数的宏定义 变长参数的宏定义是指在宏定义中参数列表的最后一个参数为省略号 而预定义宏 VA ARGS 则可以在宏定义的实现部分替换省略号所代表的

随机推荐

  • 1001 害死人不偿命的(3n+1)猜想 (15 分)

    标题 include
  • 【Vue3】学习笔记-reactive响应式

    Vue3 学习笔记 reactive响应式 用ref 设置响应式对象 用reactive 设置响应式对象 总结 用ref 设置响应式对象 JS中设置对象 import ref from vue var user ref username W
  • 2022年美国大学生数学建模-【美赛】A题:Game Theory in Cycling(附获奖论文)

    目录 Summary 1 Introduction 1 1 Problem Background 1 2 Restatement of the Problem 1 3 Our Work 2 Assuptions and Justifific
  • comsol学习中心:几何建模

    创建二维几何 我们打算创建这样的二维模型 这里演示创建 因此不考虑物理场等的设置 创建空白模型 创建的是二维几何 所以在组件中选择天剑二维组件 也可以通过在功能树上右键进行此操作 接着在几何选项卡下找到体素开始构建几何 先添加一个圆形 在功
  • mybaits如何防止SQL注入:mybatis的${}和#{}

    其实这个mybatis的 和 区别和使用 算是很古早很常见的一个基础问题了 先说结论 尽可能使用 不使用 因为 可以防止SQL注入 如果记不清楚 就记一句话或者是口诀 不是所有事都能靠钱能解决 号是货币符号 为什么 可以防止SQL注入 占位
  • Source Insight设置黑色背景

    今天使用Source Insight看C代码 觉得背景白色太亮 觉得应该可以调背景颜色 通过百度 搜索到了CSDN上的相关文章 受益良多 但是文章后面附的style文件下载需30积分 无奈囊中羞涩 只好自己按照文章的说明调颜色 首先将背景调
  • 【基于python实现UI自动化】4.1 selenium发送163邮箱邮件

    python UI自动化 1 0 selenium工具介绍 2 0 selenium环境搭建 3 Selenium的元素定位 3 0 selenium常见8大元素定位 3 1 selenium通过By定位元素 3 2 selenium通过J
  • MySQL出现:ERROR 2003 (HY000): Can't connect to MySQL server on 'localhost' (10061)问题解决

    本文mysql的安装环境为win7 64位 mysql版本为MySQL5 7 问题描述 在命令行输入 mysql u root p 登录mysql 返回 Can t connect to MySQL server on localhost
  • 【不忘初心】Windows11 22000.168 X64 四合一[纯净精简版][2.77G](2021.8.29)

    此版可正常更新补丁 WIN11全新的UI界面出炉 可以说这一次Windows 11全新升级 无论是从Logo上还是UI界面设计 都有很大的变化 母版来自UUP WIN11 22000 168 为了保证稳定初心的系统全部都是离线精简和优化 非
  • MySQL常用配置详解

    目录 一 MySQL 查看配置信息 二 MySQL 查看服务器当前运行状态的信息 三 MySQL 常用配置详解 1 mysql 使用mysql命令登录数据库时的默认的设置 2 client 客户端默认设置内容 3 mysqld 服务端端配置
  • centos 6.4/redhat 6.4 安装gitlab

    为什么80 的码农都做不了架构师 gt gt gt 一 把所有包升级到最新版本 yum y upgrade 二 安装最新版ruby 2 1 5 步骤http my oschina net duolus blog 348353 三 安装官方给
  • MATLAB对RGB彩色图像进行加马赛克处理

    简单实现MATLAB对RGB彩色图像进行加马赛克处理 为了加深对图像中像素块操作的记忆 利用像素块内均值方式对RGB彩色图像进行马赛克效果的处理 为了能后比较简单得实现 所以采用了n n像素块大小的均值的方式 为了比较简单实现 这里对RGB
  • 简单算法之矩阵运算

    不多说上代码 矩阵对象 class Matrix 矩阵的宽度 private final int width 矩阵的高度 private final int height 矩阵 private final int arr Matrix in
  • 如何创建微信小程序并实现快速变现?

    工作之余做点副业增进一下自己的收入是每个技术从业人员甚至是非技术人员都很喜欢的事情 即所谓的 睡后收入 疫情几年没时间或者说没有精力 随着疫情结束 经济的急速上升 谁不想赶快充实一下自己腰包 微信小程序毋容置疑是比较好的一种方式 做好的微信
  • 语音中的响度,音调与音色的决定因素

    目录 一 先验知识 1 基波与谐波 2 基音与泛音 二 图解泛音的形成原理 生动形象的视频介绍 三 图解泛音决定音色 写在前面 响度由声源的振幅决定 音调由基波的频率决定 音色由泛音决定 一 先验知识 1 基波与谐波 基波是原合成的周期信号
  • 毕业设计-基于机器视觉的室内智能安防车系统 -STM32和 OpenCV

    目录 前言 课题背景和意义 实现技术思路 一 系统整体设计 二 智能安防车硬件系统设计 三 室内安防车自动巡逻机制的实现 四 基于 OpenCV 图像识别的火焰识别 五 分类器设计及软件实现 代码部分 实现效果图样例 最后 前言 大四是整个
  • 主成分分析法确定权重

    在数模中 确定权重方法有很多种 比如主成分分析法 层次分析法 熵权法 相关系数作为权重等 网上很多教程都是用spss计算权重 这里主要讲利用python通过主成分分析法确定权重 主成分分析法概述 主成分分析法是一种线性的降维算法 通过将N维
  • 黑莓QNX选定由延锋伟世通数字仪表项目

    锋影 e mail 174176320 qq com 黑莓有限公司宣布延锋伟世通 全球1级汽车零部件供应商 已经选择该公司的安全认证QNX平台仪器集群1一个数字仪表项目与中国的一个主要的OEM软件 该交易的条款是保密的 黑莓的QNX技术已为
  • istio安装实践

    启动minikube istio的实验环境部署在minikube上 首先启动minikube minikube安装参考上一博客 minikube start vm driver kvm2 extra config controller ma
  • 提高C++性能的编程技术笔记:引用计数+测试代码

    引用计数 reference counting 基本思想是将销毁对象的职责从客户端代码转移到对象本身 对象跟踪记录自身当前被引用的数目 在引用计数达到零时自行销毁 换句话说 对象不再被使用时自行销毁 引用计数和执行速度之间的关系是与上下文紧