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

2023-11-06

为了使多个线程并发地分配和释放内存,必须在分配器方法中添加互斥锁。

全局内存管理器(通过new()和delete()实现)是通用的,因此它的开销也非常大。

因为单线程内存管理器要比多线程内存管理器快的多,所以如果要分配的大多数内存块限于单线程中使用,那么可以显著提升性能。

如果开发了一套有效的单线程分配器,那么通过模板可以方便地将它们扩展到多线程环境中。

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

#include "multi_threaded_memory_pool.hpp"
#include <iostream>
#include <chrono>
#include <string>
#include <mutex>

namespace multi_threaded_memory_pool_ {

// reference: 《提高C++性能的编程技术》:第七章:多线程内存池


class Rational1 {
public:
	Rational1(int a = 0, int b = 1) : n(a), d(b) {}
private:
	int n; // 分子
	int d; // 分母
};


// 单线程可变大小内存管理器: 
// MemoryChunk类取代之前版本中使用的NextOnFreeList类,它用来把不同大小的内存块连接起来形成块序列
class MemoryChunk {
public:
	MemoryChunk(MemoryChunk* nextChunk, size_t chunkSize);
	// 析构函数释放构造函数获得的内存空间
	~MemoryChunk() { delete [] mem; }

	inline void* alloc(size_t size);
	inline void free(void* someElement);

	// 指向列表下一内存块的指针
	MemoryChunk* nextMemChunk() { return next; }
	// 当前内存块剩余空间大小
	size_t spaceAvailable() { return chunkSize - bytesAlreadyAllocated; }
	// 这是一个内存块的默认大小
	enum { DEFAULT_CHUNK_SIZE = 4096 };

private:
	MemoryChunk* next;
	void* mem;

	// 一个内存块的默认大小
	size_t chunkSize;
	// 当前内存块中已分配的字节数
	size_t bytesAlreadyAllocated;
};

// 构造函数首先确定内存块的适当大小,然后根据这个大小从堆上分配私有存储空间
// MemoryChunk将next成员指向输入参数nextChunk, nextChunk是列表先前的头部
MemoryChunk::MemoryChunk(MemoryChunk* nextChunk, size_t reqSize)
{
	chunkSize = (reqSize > DEFAULT_CHUNK_SIZE) ? reqSize : DEFAULT_CHUNK_SIZE;
	next = nextChunk;
	bytesAlreadyAllocated = 0;
	mem = new char[chunkSize];
}

// alloc函数处理内存分配请求,它返回一个指针,该指针指向mem所指向的MemoryChunk私有存储空间中的可用空间。
// 该函数通过更新该块中已分配的字节数来记录可用空间的大小
void* MemoryChunk::alloc(size_t requestSize)
{
	void* addr = static_cast<void*>(static_cast<char*>(mem) + bytesAlreadyAllocated);
	bytesAlreadyAllocated += requestSize;

	return addr;
}

// 在该实现中,不用担心空闲内存段的释放。当对象被删除后,整个内存块将被释放并且返回到堆上
inline void MemoryChunk::free(void* doomed)
{
}

// MemoryChunk只是一个辅助类,ByteMemoryPoll类用它来实现可变大小的内存管理
class ByteMemoryPool {
public:
	ByteMemoryPool(size_t initSize = MemoryChunk::DEFAULT_CHUNK_SIZE);
	~ByteMemoryPool();

	// 从私有内存池分配内存
	inline  void* alloc(size_t size);
	// 释放先前从内存池中分配的内存
	inline void free(void* someElement);

private:
	// 内存块列表,它是我们的私有存储空间
	MemoryChunk* listOfMemoryChunks = nullptr;
	// 向我们的私有存储空间添加一个内存块
	void expandStorage(size_t reqSize);
};

// 虽然内存块列表可能包含多个块,但只有第一块拥有可用于分配的内存。其它块表示已分配的内存。
// 列表的首个元素是唯一能够分配可以内存的块。

// 构造函数接收initSize参数来设定一个内存块的大小,即构造函数借此来设置单个内存块的大小。
// expandStorage方法使listOfMemoryChunks指向一个已分配的MemoryChunk对象
// 创建ByteMemoryPool对象,生成私有存储空间
ByteMemoryPool::ByteMemoryPool(size_t initSize)
{
	expandStorage(initSize);
}

// 析构函数遍历内存块列表并且删除它们
ByteMemoryPool::~ByteMemoryPool()
{
	MemoryChunk* memChunk = listOfMemoryChunks;

	while (memChunk) {
		listOfMemoryChunks = memChunk->nextMemChunk();
		delete memChunk;
		memChunk = listOfMemoryChunks;
	}
}

// alloc函数确保有足够的可用空间,而把分配任务托付给列表头的MemoryChunk
void* ByteMemoryPool::alloc(size_t requestSize)
{
	size_t space = listOfMemoryChunks->spaceAvailable();
	if (space < requestSize) {
		expandStorage(requestSize);
	}

	return listOfMemoryChunks->alloc(requestSize);
}

// 释放之前分配的内存的任务被委派给列表头部的MemoryChunk来完成
// MemoryChunk::free不做任何事情,因为ByteMemoryPool的实现不会重用之前分配的内存。如果需要更多内存,
// 我们将创建新的内存块以便今后分配使用。在内存池被销毁时,内存释放回堆中。ByteMemoryPool析构函数
// 释放所有的内存块到堆中
inline void ByteMemoryPool::free(void* doomed)
{
	listOfMemoryChunks->free(doomed);
}

// 若遇到内存块用尽这种不太可能的情况,我们通过创建新的内存块并把它添加到内存块列表的头部来扩展它
void ByteMemoryPool::expandStorage(size_t reqSize)
{
	listOfMemoryChunks = new MemoryChunk(listOfMemoryChunks, reqSize);
}


// 多线程内存池实现
template<class POOLTYPE, class LOCK>
class MTMemoryPool {
public:
	// 从freeList里分配一个元素
	inline void* alloc(size_t size);
	// 返回一个元素给freeList
	inline void free(void* someElement);

private:
	POOLTYPE stPool; // 单线程池
	LOCK theLock;
};

// alloc方法将分配任务委托给内存池成员,而将锁定任务委托给锁成员
template<class M, class L>
inline void* MTMemoryPool<M, L>::alloc(size_t size)
{
	void* mem;
	theLock.lock();
	mem = stPool.alloc(size);
	theLock.unlock();

	return mem;
}

template<class M, class L>
inline void MTMemoryPool<M, L>::free(void* doomed)
{
	theLock.lock();
	stPool.free(doomed);
	theLock.unlock();
}

class ABCLock { // 抽象基类
public:
	virtual ~ABCLock() {}
	virtual void lock() = 0;
	virtual void unlock() = 0;
};

class MutexLock : public ABCLock {
public:
	MutexLock() {}
	~MutexLock() {}

	inline void lock() { mtx.lock(); }
	inline void unlock() { mtx.unlock(); }

private:
	std::mutex mtx; 
};

class Rational2 {
public:
	Rational2(int a = 0, int b = 1) : n(a),d(b) {}

	void* operator new(size_t size) { return memPool->alloc(size); }
	void operator delete(void* doomed, size_t size) { memPool->free(doomed); }

	static void newMemPool() { memPool = new MTMemoryPool<ByteMemoryPool, MutexLock>; }
	static void deleteMemPool() { delete memPool; }

private:
	int n; // 分子
	int d; // 分母
	static MTMemoryPool<ByteMemoryPool, MutexLock>* memPool;
};

MTMemoryPool<ByteMemoryPool, MutexLock>* Rational2::memPool = nullptr;

///
// 多线程内存池实现应用在单线程环境中
class DummyLock : public ABCLock {
public:
	inline void lock() {}
	inline void unlock() {}
};

class Rational3 {
public:
	Rational3(int a = 0, int b = 1) : n(a),d(b) {}

	void* operator new(size_t size) { return memPool->alloc(size); }
	void operator delete(void* doomed, size_t size) { memPool->free(doomed); }

	static void newMemPool() { memPool = new MTMemoryPool<ByteMemoryPool, DummyLock>; }
	static void deleteMemPool() { delete memPool; }

private:
	int n; // 分子
	int d; // 分母
	static MTMemoryPool<ByteMemoryPool, DummyLock>* memPool;
};

MTMemoryPool<ByteMemoryPool, DummyLock>* Rational3::memPool = nullptr;

int test_multi_threaded_memory_pool_1()
{
	using namespace std::chrono;
	high_resolution_clock::time_point time_start, time_end;
	const int cycle_number1{10000}, cycle_number2{1000};

{ // 测试全局函数new()和delete()的基准性能
	Rational1* array[cycle_number2];

	time_start = high_resolution_clock::now();
	for (int j =0; j < cycle_number1; ++j) {
		for (int i =0; i < cycle_number2; ++i) {
			array[i] = new Rational1(i);
		}

		for (int i = 0; i < cycle_number2; ++i) {
			delete array[i];
		}	
	}
	time_end = high_resolution_clock::now();

	fprintf(stdout, "global function new/delete time spent: %f seconds\n",(duration_cast<duration<double>>(time_end - time_start)).count());
}

{ // 多线程内存池测试代码
	Rational2* array[cycle_number2];

	time_start = high_resolution_clock::now();
	Rational2::newMemPool();

	for (int j = 0; j < cycle_number1; ++j) {
		for (int i = 0; i < cycle_number2; ++i) {
			array[i] = new Rational2(i);
		}

		for (int i = 0; i < cycle_number2; ++i) {
			delete array[i];
		}
	}

	Rational2::deleteMemPool();
	time_end = high_resolution_clock::now();

	fprintf(stdout, "multi-threaded variable-size memory manager time spent: %f seconds\n",(duration_cast<duration<double>>(time_end - time_start)).count());	
}

{ // 多线程内存池应用在单线程环境下测试代码
	Rational3* array[cycle_number2];

	time_start = high_resolution_clock::now();
	Rational3::newMemPool();

	for (int j = 0; j < cycle_number1; ++j) {
		for (int i = 0; i < cycle_number2; ++i) {
			array[i] = new Rational3(i);
		}

		for (int i = 0; i < cycle_number2; ++i) {
			delete array[i];
		}
	}

	Rational3::deleteMemPool();
	time_end = high_resolution_clock::now();

	fprintf(stdout, "multi-threaded variable-size memory manager in single-threaded environment time spent: %f seconds\n",(duration_cast<duration<double>>(time_end - time_start)).count());	
}

	return 0;
}

} // namespace multi_threaded_memory_pool_

执行结果如下:

GitHubhttps://github.com/fengbingchun/Messy_Test  

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

提高C++性能的编程技术笔记:多线程内存池+测试代码 的相关文章

  • Linux下创建进程简介

    在博文https blog csdn net fengbingchun article details 108940548中简单介绍了Windows下通过函数CreateProcess创建进程的过程 这里简单介绍下Linux下通过fork函
  • C和C++安全编码笔记:整数安全

    5 1 整数安全导论 整数由包括0的自然数 0 1 2 3 和非零自然数的负数 1 2 3 构成 5 2 整数数据类型 整数类型提供了整数数学集合的一个有限子集的模型 一个具有整数类型的对象的值是附着在这个对象上的数学值 一个具有整数类型的
  • C语言中的弱符号与强符号介绍

    弱符号 Weak symbol 是链接器 ld 在生成ELF Executable and Linkable Format 缩写为ELF 可执行和可链接格式 是一种用于可执行文件 目标文件 共享库和核心转储的标准文件格式 ELF文件有两种索
  • C++/C++11中头文件algorithm的使用

  • C++中namespace detail或namespace internal的使用

    在很多开源代码中偶尔会使用名字为 detail 或 internal 的命名空间 如OpenCV的modules目录中 有些文件中使用了namespace detail 有些文件中使用了namespace internal 名为detail
  • 程序员的自我修养--链接、装载与库笔记:可执行文件的装载与进程

    可执行文件只有装载到内存以后才能被CPU执行 1 进程虚拟地址空间 程序和进程有什么区别 程序 或者狭义上讲可执行文件 是一个静态的概念 它就是一些预先编译好的指令和数据集合的一个文件 进程则是一个动态的概念 它是程序运行时的一个过程 很多
  • 概率论中高斯分布(正态分布)介绍及C++11中std::normal_distribution的使用

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

    原子库为细粒度的原子操作提供组件 允许无锁并发编程 涉及同一对象的每个原子操作 相对于任何其他原子操作是不可分的 原子对象不具有数据竞争 data race 原子类型对象的主要特点就是从不同线程访问不会导致数据竞争 因此从不同线程访问某个原
  • Windows下创建进程简介

    正在执行的应用程序称为进程 进程不仅仅是指令和数据 它还有状态 状态是保存在处理器寄存器中的一组值 如当前执行指令的地址 保存在内存中的值 以及唯一定义进程在任一时刻任务的所有其他值 进程与应用程序的一个重要的区别在于 进程运行时 进程的状
  • log库spdlog简介及使用

    spdlog是一个开源的 快速的 仅有头文件的C 11 日志库 code地址在 https github com gabime spdlog 目前最新的发布版本为0 14 0 它提供了向流 标准输出 文件 系统日志 调试器等目标输出日志的能
  • 提高C++性能的编程技术笔记:多线程内存池+测试代码

    为了使多个线程并发地分配和释放内存 必须在分配器方法中添加互斥锁 全局内存管理器 通过new 和delete 实现 是通用的 因此它的开销也非常大 因为单线程内存管理器要比多线程内存管理器快的多 所以如果要分配的大多数内存块限于单线程中使用
  • Linux下遍历指定目录的C++实现

    之前在 https blog csdn net fengbingchun article details 51474728 给出了在Windows遍历指定文件夹的C 实现 这里给出在Linux下遍历目录的实现 Windows和Linux下的
  • 提高C++性能的编程技术笔记:引用计数+测试代码

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

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

    使用C 14中的auto返回类型 编译器将尝试自动推导 deduce 返回类型 namespace int xx 1 auto f return xx return type is int const auto f3 return xx r
  • C++中插件使用举例

    插件并不是在构建时链接的 而是在运行时发现并加载的 因此 用户可以利用你定义好的插件API来编写自己的插件 这样他们就能以指定方式扩展API的功能 插件库是一个动态库 它可以独立于核心API编译 在运行时根据需要显示加载 不过插件也可以使用
  • C++中的封装、继承、多态

    封装 encapsulation 就是将抽象得到的数据和行为 或功能 相结合 形成一个有机的整体 也就是将数据与操作数据的源代码进行有机的结合 形成 类 其中数据和函数都是类的成员 封装的目的是增强安全性和简化编程 使用者不必了解具体的实现
  • C++中typeid的使用

    RTTI Run TimeType Information 运行时类型信息 它提供了运行时确定对象类型的方法 在C 中 为了支持RTTI提供了两个操作符 dynamic cast和typeid The typeid operator pro
  • 概率论中伯努利分布(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++11中thread_local的使用

    C 11中的thread local是C 存储期的一种 属于线程存储期 存储期定义C 程序中变量 函数的范围 可见性 和生命周期 C 程序中可用的存储期包括auto register static extern mutable和thread

随机推荐

  • 【踩坑专栏】0%classes,0% lines covered

    这东西一般都是不小心点到debug按钮右边的coverage按钮出现的 解决办法 Ctrl Alt F6 取消勾选你的应用 点击最左侧的show detected 或直接点击下方中间的no coverage 参考文章 1 IDEA 项目结构
  • python连接数据库

    参考python核心编程 编写一个用户洗牌的脚本 根据用户输入 选择连接sqlserver或者MySQL 创建数据库 表 随机生成数据 并实现增删改查 其中 为了兼容python2和python3 统一了打印函数 录入函数 动态导包等 一些
  • mysql怎么让表中某一列字段按某字符分割一行变成多行

    注意暂时看不懂的请看下列的解析方法 代码下面有具体解释 SELECT a XH substring index substring index a QZYSBM b help topic id 1 1 AS splitName FROM S
  • 【C++】deque容器

    0 前言 1 deque构造函数 include
  • 计网实验A3:简单的web服务器

    文章目录 计网实验A3 简单的web服务器 实验介绍 相关背景介绍 Socket编程接口 HTTP传输协议 实验功能要求 总体设计 详细设计 数据结构设计 函数分析 调试设计 运行结果 实验总结 困难与解决 心得与思考 计网实验A3 简单的
  • Andrew Ng机器学习算法入门((六):多变量线性回归方程求解

    多变量线性回归 之前讨论的都是单变量的情况 例如房价与房屋面积之前的关系 但是实际上 房价除了房屋面积之外 还要房间数 楼层等因素相关 那么此时就变成了一个多变量线性回归的问题 在实际问题中 多变量的线性回归问题是更加常见的 下面这个例子就
  • Tomcat 相关配置参数说明,性能调优

    Tomcat 相关配置参数说明 1 server xml connect中相关参数说明
  • 爬虫简单爬取网页图片

    仅供学习 请遵守法律法规和robots协议 请在爬取时设置爬取延时 防止给网站造成不必要的麻烦和损失 也避免给自己送进去 爬取图片一般需要导入的库有 import requests import re 正则表达式 import os os用
  • 多线程提高spark streaming数据写入到数据库

    多线程提高spark streaming数据写入到数据库 需求 集群环境资源有限 需要跑多个spark streaming任务 每个任务必须占据1核 cpu利用率很低 需要对数据进行实时统计更新到数据库mysql给业务实时展示 数据聚合程度
  • java-打印项目相对路径的根目录

    IDEA里 System out println System getProperty user dir
  • Java中栈Stack的bug(继承) 以及自己实现一个栈 支持范型 动态扩展

    问题 解决一 封装Stack 解决二 自己实现 Array java ArrayStack java 问题 import java util Stack public class Main public static void main S
  • 【论文笔记】对比学习综述

    跟李沐学AI的b站视频视频 论文精读笔记第五期 https www bilibili com s video BV19S4y1M7hm 最后有总结 请添加图片描述
  • Java 中通过 key 获取锁的正确方式

    一 概览 本文我们将了解如何通过特定键获取锁 以保证该键上的操作的线程安全 并且不妨碍其他键 一般来说 我们需要实现两个方法 void lock String key void unlock String key 本文以字符串作为键为例 大
  • hive报错FAILED: SemanticException org.apache.hadoop.hive.ql.metadata.HiveException

    问题描述 创建表时 没有问题 简单插入数据出现这个报错 hive报错FAILED SemanticException org apache hadoop hive ql metadata HiveException Failed to ex
  • ffmpeg windows编译及调试完整版

    目录 编译 基础环境准备 依赖环境安装 依赖库安装 X264 fdk aac X265 ffmpeg 4 3 6 调试 基础项目环境搭建 VS2019项目创建 VS2019项目代码 vs2019配置 VS2019调试 编译 基础环境准备 1
  • 多行字符串无法匹配问题处理

    关于多行字符串无法匹配的问题 处理当前问题 需要加上下面的东西 Pattern MULTILINE Pattern DOTALL 这样才能处理多行问题 防止拿不到匹配符中的效果 这里简单的处理方式入如下所示 Pattern patternC
  • 《MySQL实战45讲》读后感 21

    在上一篇文章中 我和你介绍了间隙锁和 next key lock 的概念 但是并没有说明加锁规则 间隙锁的概念理解起来确实有点儿难 尤其在配合上行锁以后 很容易在判断是否会出现锁等待的问题上犯错 所以今天 我们就先从这个加锁规则开始吧 首先
  • aspose java api_NetBeans中如何下载并使用Aspose Java API和示例

    为了提升文件格式应用程序界面 API 的用户体验 Java NetBeans是Java开发人员在管理Java项目 开发组件或应用程序时使用最多的集成开发环境之一 为了让他们在NetBeans项目中通过一个简单的点击就能下载和使用多个Aspo
  • NITIRE 2023官方的PSNR及SSIM计算代码

    NITIRE 2023官方的PSNR及SSIM计算代码 问题描述 做图像复原任务时 总避免不了计算PSNR和SSIM等图像质量评估指标 但是网上实在是太多计算这类指标的代码了 不同代码计算的结果还可能存在差异 有使用matlab计算SSIM
  • 提高C++性能的编程技术笔记:多线程内存池+测试代码

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