Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >【项目日记】高并发内存池---细节优化及性能测试

【项目日记】高并发内存池---细节优化及性能测试

作者头像
叫我龙翔
发布于 2024-09-08 04:44:29
发布于 2024-09-08 04:44:29
22100
代码可运行
举报
运行总次数:0
代码可运行

1 细节优化

在前面的文章中我们已经实现了高并发内存池的申请内存逻辑和释放内存逻辑:

  • 申请逻辑:
    1. 首先在线程缓存中查看是否有内存块可以直接使用,没有就去中心缓存申请一批内存块。
    2. 中心缓存中的span中有未使用的合适内存块就进行返回,没有就去页缓存中申请合适页数的span。
    3. 页缓存中如果有合适的span就进行返回,没有就查找有没有页数更大的span 。如果有页数大的span有进行拆分,没有就去申请最大页数的span进行拆分!
  • 释放逻辑:
    1. 线程缓存回收内存块,挂载到合适的自由链表中。如果挂载的内存块数量到达一定标准就进行回收。
    2. 回收的内存块还给中心缓存,中心缓存根据内存块的地址归还给对应的span进挂载。当span的引用计数为0时就进行回收
    3. 页缓存得到span之后,先来进行前后页的合并,将span尽可能的合并成更大的span。

接下来我们就来解决一下一些细节问题:

1.1 大块内存的申请处理

根据我们书写的代码,线程缓存中最大挂载的内存块大小是256KB,当我们申请大于256KB的内存时,显然现场缓存是不能满足要求的,而由于中心缓存的映射关系和线程缓存是一致的,所以想要申请大于256KB的内存块,就需要去页缓存中直接申请。大于256KB的内存我们按照页的大小来进行内存对齐,所以大于256KB的内存我们就实际上是去获取对应页的span。我们获取到span之后再按照页号复原出地址进行返回就可以了!

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
static void* ConcurrentAlloc(size_t size)
{
	//如果size的空间大于的32页,就不能在线程缓存里进行取了
	//因为线程缓存中最大的内存块是256KB 
	//超过32页就需要取页缓存中去进行取了
	if (size > MAX_BYTES)
	{
		//计算对齐空间
		size_t alignsize = SizeClass::RoundUp(size);
		//计算页数
		size_t num = alignsize >> PAGE_SHIFT;
		//根据页数开辟span
		PageCache::GetInstance()->GetMutex().lock();
		Span * span = PageCache::GetInstance()->NewSpan(num);
		PageCache::GetInstance()->GetMutex().unlock();
		span->_objsize = alignsize;
		//根据span中的_pageid计算地址
		void* ptr = (void*)(span->_pageid << PAGE_SHIFT);
		//进行返回即可
		return ptr;
	}
	//在该线程中进行内存的申请
	if (pThreadCache == nullptr)
	{
		static ObjectPool<ThreadCache> tcpool;
		//pThreadCache = new ThreadCache;
		pThreadCache = tcpool.New();
	}
	//进行开辟空间
	void * obj = pThreadCache->Allocate(size);
	//cout << std::this_thread::get_id() << " : " << pThreadCache << endl;
	return obj;
}

回收大内存时,也进行特殊处理,跳过线程缓存直接回收到页缓存中就可以了!

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
static void  ConcurrentFree(void* ptr)
{
	assert(ptr);
	//找到ptr对应的span即可找到字节数
	//在span创建完之后传回给中心缓存挂载起来时就已经确定了其内存块的大小!!!
	Span* span = PageCache::GetInstance()->MapObjectToSpan(ptr);
	size_t size = span->_objsize;
	//如果size大于256KB就要跳过线程缓存和中心缓存
	if (size > MAX_BYTES)
	{
		//直接回收该空间到页缓存中
		PageCache::GetInstance()->GetMutex().lock();
		PageCache::GetInstance()->ReleaseSpanToPageCache(span);
		PageCache::GetInstance()->GetMutex().unlock();
		return;
	}
	//回收空间
	pThreadCache->Deallocate(ptr, size);
}

大于256KB的情况中还有一种特殊情况,当申请的空间页数大于128页时,页缓存也无法对其进行管理。因为页缓存中最大的span就是128页的,针对这种情况,我们的内存池就不能发挥作用了,所以我们直接使用系统调用来申请者篇大空间就可以了,回收时一样特殊处理。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Span* PageCache::NewSpan(size_t num)
{
	// 如果开辟的页数大于 128 页 内存池就不能发挥作用了
	// 需要直接去堆上申请
	if (num >= PAGENUM)
	{
		//开辟对应的大空间
		void* ptr = SystemAlloc(num);
		//创建span
		//Span* newspan = new Span;
		//使用定长池 跳过new(malloc)
		Span* newspan = _spanpool.New();
		newspan->_pageid = (PAGE_ID)ptr >> PAGE_SHIFT;//页号
		newspan->_n = num;//页数
		//建立哈希映射
		_SpanMap[newspan->_pageid] = newspan;
		//进行返回

		return newspan;
	}
	// 1 - 128
	//...省略...
	//.........
}
void PageCache::ReleaseSpanToPageCache(Span* span)
{
	//如果页数大于128 不能进行合并 直接返回给系统
	if (span->_n > PAGENUM - 1)
	{
		void* ptr = (void*)(span->_pageid << PAGE_SHIFT);
		//解除映射
		_SpanMap.erase(span->_pageid);

		_spanpool.Delete(span);

		SystemFree(ptr);
		

		return;
	}
	//1 -128
	//......
	//......
}

这就是对大内存的处理方法,这一步的优化其实就是将特殊情况给补全!

1.2 配合定长池脱离使用new

我们的项目是为了替代掉C++中原始的new(malloc),使用自己的一套体系来做到内存申请。但是我们在实现高并发内存池是使用很多的new来创建span,为了解决这个问题,我们引入我们最开始实现的定长池:定长池思路分析

定长池内部是:

  1. 一段连续的大空间:我们使用char*来进行管理,方便一个一个字节来进行处理。
  2. 一个自由链表:进行资源的回收使用,内部通过链表结构链接起来
  3. 剩余资源数:进行资源空间的管理,不足够是进行扩容!

通过对开辟出来的大空间进行拆分实现减少内存碎片!

我们可以在页缓存中加入一个管理span对象的定长池,每次newspan的时候就换成从定长池中进行取出。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class PageCache
{
public:
	Span* NewSpan(size_t num);
	std::mutex& GetMutex();
	void ReleaseSpanToPageCache(Span* span);
	Span* MapObjectToSpan(void* start);
private:
	//...
	//对象池 方便进行取出span
	ObjectPool<Span> _spanpool;
	//...
	//...
}

然后将所有的new都更换为_spanpool.New()就可以了!

1.3 释放对象无需内存大小

我们的项目最终是要替代new(malloc)的,我们在使用new的时候并不需要去加入开辟的空间,系统会自动识别开辟空间的大小。那么在我们的程序中我们需要如何优化才可以不需要在加入内存大小呢?

首先,我们申请的空间都为内存块,内存块在创建的时候是挂载在span上的,每个span都有自己独一无二的页号。所以其实页号就可以对应其内存块的大小。为了达到这样的效果我们可以建立一个哈希表,来记录页号对应的内存块大小。但更简单的做法是在span中记录一个内存块大小的成员变量,这样我们可以通过地址找到span,,也就找到了内存块大小!!!

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
static void  ConcurrentFree(void* ptr)
{
	assert(ptr);
	//找到ptr对应的span即可找到字节数
	//在span创建完之后传回给中心缓存挂载起来时就已经确定了其内存块的大小!!!
	Span* span = PageCache::GetInstance()->MapObjectToSpan(ptr);
	size_t size = span->_objsize;
	//如果size大于256KB就要跳过线程缓存和中心缓存
	if (size > MAX_BYTES)
	{
		//直接回收该空间到页缓存中
		PageCache::GetInstance()->GetMutex().lock();
		PageCache::GetInstance()->ReleaseSpanToPageCache(span);
		PageCache::GetInstance()->GetMutex().unlock();
		return;
	}
	//回收空间
	pThreadCache->Deallocate(ptr, size);
}

这样就不在需要再free时加上内存大小了,使用起来方便了很多!!!

2 调试Debug

性能测试之前,首先我们要确保我们的代码没有了问题,由于编写代码时的不仔细,导致我进行了很长时间的Debug ,快给孩子整崩溃了o(╥﹏╥)oo(╥﹏╥)oo(╥﹏╥)o

在Debug过程中,VS2022提供了很多实用的功能:

  1. 并行监视:可以监视多个线程的执行情况
  2. 调用堆栈:在程序崩溃时可以返回到上层的调用函数
  3. 内存对比:解决内存块问题,无法通过监视来查看链式机构,通过内存窗口可以方便查看

程序崩溃时,通过条件断点逐层向上检查:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
if()
{
	int x = 0 ;//打上断点
}

这样可以帮助我们更快的解决bug,但还是需要一定经验,不然会和我一样进行好长好长时间的调试,真的太难受了!

3 性能测试

现在我们来进行一下高并发内存池与new的性能对比,我们分别使用两种方式来进行开辟若干的内存块:

可以看到我们现在的内存池开辟释放空间的效率其实和原生new差不多,这可不好,因为谷歌开源的tcmalloc可以提升至原生new效率的4倍。我们现在到底是哪里的效率比较低呢???我们可以通过VS2022的性能测试工具来进行检测:

这里如果报出了无法生成的错误,说明我们需要进行一个简单配置,将我们的程序加上/profile属性

之后我们就可以来看效率中影响最大的部分是哪里:

根据调用的函数可以推断出: 主要耗时是在锁的竞争中,在central中因为使用的是桶锁所以对性能的影响不大。但是在pagecache文件中的函数耗时都比较久,其实我们隐约已经知道问题出现在哪里了,首先unordered_map的底层是哈希桶结构,哈希桶内部不是线程安全的,其次所以每次对页缓存进行操作时都要将整个页缓存锁住,这其实就是影响性能的主要因素。

那么解决这个问题的首要问题就可不可以将锁去除,使用一直无需加锁的哈希表结构呢?tcmalloc中的解决方案是基数树,一种特殊的哈希表。其主要有三种:

  1. 单层结构:线性映射的大数组,通过页号直接映射。
  2. 双层结构:二维映射的哈希表,将一个页号进行两部分的拆分。
  3. 三层结构:三维映射的哈希表,将一个页号进行三部分的拆分。

为什么基数树不需要加锁? 因为基数树的读写是独立的,当一个线程正在处理一个页号对应的span时,如果有另外一个线程想要进行写入,这是完全不会互相影响的,因为基数树是一个大数组,其内部结构确定,通过页号来进行行映射,不会因为新写入一个数据而改变结构。

unordered_map需要加锁,是因为读写并不独立,写入时会改变底层哈希桶的结构,可能导致读取出错!

这里给出三种结构的基数树,32位可以使用双层结构,64位使用三层结构。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include"Common.h"

// --- 参考tc-malloc源码的基数树优化 ---
//可以实现无锁操作 --- 大大提升性能
//基数树的本质也是哈希表
// 1. 1层的结构就是通过一个大的指针数组来指向span
// 2. 2层的结构就是通过二维数组来实现 比如32位下 8KB页,会有2^19个页号
//	  前5位确认在哪个指针数组 后13位确定具体指针
// 3 3层的同理使用三维数组

// Single-level array
template <int BITS>
class TCMalloc_PageMap1 {
private:
	static const int LENGTH = 1 << BITS;
	void** array_;

public:
	typedef uintptr_t Number;

	//explicit TCMalloc_PageMap1(void* (*allocator)(size_t)) {
	explicit TCMalloc_PageMap1() {
		//array_ = reinterpret_cast<void**>((*allocator)(sizeof(void*) << BITS));
		size_t size = sizeof(void*) << BITS;
		size_t alignSize = SizeClass::_RoundUp(size, 1 << PAGE_SHIFT);
		array_ = (void**)SystemAlloc(alignSize >> PAGE_SHIFT);
		memset(array_, 0, sizeof(void*) << BITS);
	}

	// Return the current value for KEY.  Returns NULL if not yet set,
	// or if k is out of range.
	void* get(Number k) const {
		if ((k >> BITS) > 0) {
			return NULL;
		}
		return array_[k];
	}

	// REQUIRES "k" is in range "[0,2^BITS-1]".
	// REQUIRES "k" has been ensured before.
	//
	// Sets the value 'v' for key 'k'.
	void set(Number k, void* v) {
		array_[k] = v;
	}
};

// Two-level radix tree
template <int BITS>
class TCMalloc_PageMap2 {
private:
	// Put 32 entries in the root and (2^BITS)/32 entries in each leaf.
	static const int ROOT_BITS = 5;
	static const int ROOT_LENGTH = 1 << ROOT_BITS;

	static const int LEAF_BITS = BITS - ROOT_BITS;
	static const int LEAF_LENGTH = 1 << LEAF_BITS;

	// Leaf node
	struct Leaf {
		void* values[LEAF_LENGTH];
	};

	Leaf* root_[ROOT_LENGTH];             // Pointers to 32 child nodes
	void* (*allocator_)(size_t);          // Memory allocator

public:
	typedef uintptr_t Number;

	//explicit TCMalloc_PageMap2(void* (*allocator)(size_t)) {
	explicit TCMalloc_PageMap2() {
		//allocator_ = allocator;
		memset(root_, 0, sizeof(root_));

		PreallocateMoreMemory();
	}

	void* get(Number k) const {
		const Number i1 = k >> LEAF_BITS;
		const Number i2 = k & (LEAF_LENGTH - 1);
		if ((k >> BITS) > 0 || root_[i1] == NULL) {
			return NULL;
		}
		return root_[i1]->values[i2];
	}

	void set(Number k, void* v) {
		const Number i1 = k >> LEAF_BITS;
		const Number i2 = k & (LEAF_LENGTH - 1);
		ASSERT(i1 < ROOT_LENGTH);
		root_[i1]->values[i2] = v;
	}

	bool Ensure(Number start, size_t n) {
		for (Number key = start; key <= start + n - 1;) {
			const Number i1 = key >> LEAF_BITS;

			// Check for overflow
			if (i1 >= ROOT_LENGTH)
				return false;

			// Make 2nd level node if necessary
			if (root_[i1] == NULL) {
				//Leaf* leaf = reinterpret_cast<Leaf*>((*allocator_)(sizeof(Leaf)));
				//if (leaf == NULL) return false;
				static ObjectPool<Leaf>	leafPool;
				Leaf* leaf = (Leaf*)leafPool.New();

				memset(leaf, 0, sizeof(*leaf));
				root_[i1] = leaf;
			}

			// Advance key past whatever is covered by this leaf node
			key = ((key >> LEAF_BITS) + 1) << LEAF_BITS;
		}
		return true;
	}

	void PreallocateMoreMemory() {
		// Allocate enough to keep track of all possible pages
		Ensure(0, 1 << BITS);
	}
};

// Three-level radix tree
template <int BITS>
class TCMalloc_PageMap3 {
private:
	// How many bits should we consume at each interior level
	static const int INTERIOR_BITS = (BITS + 2) / 3; // Round-up
	static const int INTERIOR_LENGTH = 1 << INTERIOR_BITS;

	// How many bits should we consume at leaf level
	static const int LEAF_BITS = BITS - 2 * INTERIOR_BITS;
	static const int LEAF_LENGTH = 1 << LEAF_BITS;

	// Interior node
	struct Node {
		Node* ptrs[INTERIOR_LENGTH];
	};

	// Leaf node
	struct Leaf {
		void* values[LEAF_LENGTH];
	};

	Node* root_;                          // Root of radix tree
	void* (*allocator_)(size_t);          // Memory allocator

	Node* NewNode() {
		Node* result = reinterpret_cast<Node*>((*allocator_)(sizeof(Node)));
		if (result != NULL) {
			memset(result, 0, sizeof(*result));
		}
		return result;
	}

public:
	typedef uintptr_t Number;

	explicit TCMalloc_PageMap3(void* (*allocator)(size_t)) {
		allocator_ = allocator;
		root_ = NewNode();
	}

	void* get(Number k) const {
		const Number i1 = k >> (LEAF_BITS + INTERIOR_BITS);
		const Number i2 = (k >> LEAF_BITS) & (INTERIOR_LENGTH - 1);
		const Number i3 = k & (LEAF_LENGTH - 1);
		if ((k >> BITS) > 0 ||
			root_->ptrs[i1] == NULL || root_->ptrs[i1]->ptrs[i2] == NULL) {
			return NULL;
		}
		return reinterpret_cast<Leaf*>(root_->ptrs[i1]->ptrs[i2])->values[i3];
	}

	void set(Number k, void* v) {
		ASSERT(k >> BITS == 0);
		const Number i1 = k >> (LEAF_BITS + INTERIOR_BITS);
		const Number i2 = (k >> LEAF_BITS) & (INTERIOR_LENGTH - 1);
		const Number i3 = k & (LEAF_LENGTH - 1);
		reinterpret_cast<Leaf*>(root_->ptrs[i1]->ptrs[i2])->values[i3] = v;
	}

	bool Ensure(Number start, size_t n) {
		for (Number key = start; key <= start + n - 1;) {
			const Number i1 = key >> (LEAF_BITS + INTERIOR_BITS);
			const Number i2 = (key >> LEAF_BITS) & (INTERIOR_LENGTH - 1);

			// Check for overflow
			if (i1 >= INTERIOR_LENGTH || i2 >= INTERIOR_LENGTH)
				return false;

			// Make 2nd level node if necessary
			if (root_->ptrs[i1] == NULL) {
				Node* n = NewNode();
				if (n == NULL) return false;
				root_->ptrs[i1] = n;
			}

			// Make leaf node if necessary
			if (root_->ptrs[i1]->ptrs[i2] == NULL) {
				Leaf* leaf = reinterpret_cast<Leaf*>((*allocator_)(sizeof(Leaf)));
				if (leaf == NULL) return false;
				memset(leaf, 0, sizeof(*leaf));
				root_->ptrs[i1]->ptrs[i2] = reinterpret_cast<Node*>(leaf);
			}

			// Advance key past whatever is covered by this leaf node
			key = ((key >> LEAF_BITS) + 1) << LEAF_BITS;
		}
		return true;
	}

	void PreallocateMoreMemory() {
	}
};

然后将所以的_SpanMap的[]find()操作更换为set get然后将不必要的锁去除,接下来就是见证奇迹的时刻了:

可以看到我们高并发内存池可以达到原生new性能的四倍左右!!!

4 项目总结

这样我们就完成了我们的高并发内存池项目,接下来我们可以将我们的项目打包成静态库或动态库来来进行使用

通过对属性页的修改,我们可以生成对应的静态库和动态库!至此我们的项目全部完成!

项目总结(简历版)

2024.8 - 2024.9 — 高并发内存池 —

  1. 项目描述: 开发了一个用于高并发环境的内存池管理系统,旨在提高内存分配与释放的效率,减少系统开销并优化多线程应用性能。
  2. 技术栈: C++编程语言 、多线程编程(std::thread, std::mutex, std::condition_variable)、内存管理与自定义分配器
  3. 关键实现: 实现了自定义内存池以管理内存分配,有效减少内存碎片。采用互斥锁和条件变量等技术确保线程安全的同时,尽量降低锁争用。针对多线程优化,使用策略如缓存对齐和锁分段技术,提高应用程序的运行性能。
  4. 项目挑战: 多线程环境下的非阻塞内存管理设计与实现。 通过合理策略减少内存碎片,并提高分配器效率。 使用全面的测试方案进行调试,确保在高并发环境下的稳定性。
  5. 项目成果: 提高了内存分配和释放的效率,显著降低了多线程程序的内存管理开销。
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-09-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
使用基数树优化高并发内存池(替代加锁访问的哈希表和红黑树)
本篇旨在熟悉 基于tcmalloc的高性能并发内存池项目之后,对于最后的优化进行的笔记梳理,项目完整代码 可点击:项目代码 进行查看。
用户11317877
2025/04/17
1150
使用基数树优化高并发内存池(替代加锁访问的哈希表和红黑树)
【c++实战项目】从零实现一个高并发内存池
当前项目是实现一个高并发的内存池,他的原型是google的一个开源项目tcmalloc,tcmalloc全称Thread-Caching Malloc,即线程缓存的malloc,实现了高效的多线程内存管理,用于替代系统的内存分配相关的函数(malloc、free)
用户10925563
2024/09/26
1770
【c++实战项目】从零实现一个高并发内存池
C++ ⾼性能内存池
当前项⽬是实现⼀个⾼并发的内存池,他的原型是google的⼀个开源项⽬tcmalloc,tcmalloc全称 Thread-Caching Malloc,即线程缓存的malloc,实现了⾼效的多线程内存管理,⽤于替代系统的内存分配相关的函数(malloc、free)。
ljw695
2025/02/27
1171
C++ ⾼性能内存池
【项目日记】高并发内存池---实现页缓存
首先我们来看页缓存的设计思路,明白思路,代码就可以更加舒畅的写出来,并且这个项目的调试比较困难,一定一定要仔细明白设计思路,把代码仔细写好才能保证我们的开发效率!
叫我龙翔
2024/09/06
800
【项目设计】高并发内存池
1. 本项目基于google公司的开源项目tcmalloc作为背景,简化实现一个高并发内存池,用该项目可以替代传统的malloc free函数来申请和释放内存,malloc和free作为我们最开始接触内存管理的元老级函数是在熟悉不过的了,有人说已经有malloc和free这样的内存管理函数了,我们搞一个tcmalloc真的有意义吗?其实不然,像malloc和free这种的函数是通用级别的,而通用的东西往往都带有一个特性,那就是适用性强,可移植性强,但是随之而带来的缺点就是针对性不够明显,比如对于某些高并发项目场景,项目内的线程数量众多,不断的调用malloc,可能会涉及到频繁的加锁和解锁,这对于项目性能的影响是不可小觑的,所以在某些高并发场景,同时对性能要求又高的情况下,malloc和free就显的没那么能打了,此时google公司召集了一批顶尖的cpp高手写出来了tcmalloc这样高效的内存管理项目,而我们的这个项目只是从tcmalloc里面摘取了精华部分,目的就是学习和理解高效的内存管理应该是什么样子的,而不是造一个更好的轮子出来。
举杯邀明月
2024/05/26
2901
【项目设计】高并发内存池
高并发内存池并不难~
原型是源于Google开源项目tcmalloc,其全称是Thread-Caching Malloc,即为具有线程缓存的malloc,实现了搞笑的多线程内存管理,用于代替系统的malloc和free。
小文要打代码
2025/03/20
1500
高并发内存池并不难~
【项目日记】高并发内存池---实现内存回收
根据这两个成员变量我们就可以确定span管理的空间范围,然后就可以在中心缓存中将他们按照对应内存块的大小插入到 _freelist自由链表中!
叫我龙翔
2024/09/08
2100
【项目日记】高并发内存池---实现内存回收
【项目日记】高并发内存池 ---项目介绍及组件定长池的实现
这个项目是把tcmalloc最核心的框架简化后拿出来,模拟实现出一个自己的高并发内存池,目的是为了学习tcamlloc项目的精华,谷歌大厂的项目那必是含金量十足!这种方式有点类似我们之前学习STL容器的方式。但是相比STL容器部分,tcmalloc的代码量和复杂度上升了很多,大家做好心理准备。
叫我龙翔
2024/09/06
2110
【项目日记】高并发内存池 ---项目介绍及组件定长池的实现
【项目日记】高并发内存池---实现中心缓存
实现中心缓存之前,我们先理解中心缓存需要做那些事情,具有哪些特性?我们把中心缓存的功能特性理解清楚了自然而然的就可以写出代码来!
叫我龙翔
2024/09/06
1340
【C】高并发内存池设计
高并发内存池设计 高并发下传统方式的弊端 在传统C语言中,我们使用malloc、calloc、realloc、free来进行内存的申请分配与释放,函数原型如下。C++中则是new、delete。 void *malloc(size_t size); malloc在内存的动态存储区中分配了一块长度为size字节的连续区域返回该区域的首地址。 void *calloc(size_t nmemb, size_t size); 与malloc相似,参数size为申请地址的单位元素长度,nmem
半生瓜的blog
2023/05/13
1K0
【C】高并发内存池设计
【项目日记】高并发内存池项目---整体框架设计
现代很多的开发环境都是多核多线程,在申请内存的场景下,必然会存在激烈的锁竞争问题,锁竞争会有一部分的性能损耗(因为需要阻塞等待)。malloc本身其实已经很优秀,那么我们项目的原型tcmalloc就是在多线程高并发的场景下更胜一筹,效率更加优秀。这次我们实现的内存池需要考虑以下几方面的问题:
叫我龙翔
2024/09/06
1380
【项目日记】高并发内存池项目---整体框架设计
ptmalloc、tcmalloc与jemalloc对比分析
在开发微信看一看期间,为了进行耗时优化,基础库这层按照惯例使用tcmalloc替代glibc标配的ptmalloc做优化,CPU消耗和耗时确实有所降低。但在晚上高峰时期,在CPU刚刚超过50%之后却出现了指数上升,服务在几分钟之内不可用。最终定位到是tcmalloc在内存分配的时候使用自旋锁,在锁冲突严重的时候导致CPU飙升。为了弄清楚tcmalloc到底做了什么,仔细了解各种内存管理库迫在眉睫。
233333
2024/02/23
2.3K0
ptmalloc、tcmalloc与jemalloc对比分析
tcmalloc
TCMalloc全称Thread-Caching Malloc,即线程缓存的malloc,实现了高效的多线程内存管理,用于替代系统的内存分配相关的函数(malloc、free,new,new[]等)。
golangLeetcode
2022/08/02
1.9K0
tcmalloc
【C++高并发内存池篇】性能卷王养成记:C++ 定长内存池,让内存分配快到飞起!
通过对比我们可以发现正是因为一种类型定长内存池只能申请一种类型的对象空间,这就给了这种申请方式很多优点:
羑悻的小杀马特.
2025/06/10
1260
【C++高并发内存池篇】性能卷王养成记:C++ 定长内存池,让内存分配快到飞起!
高并发内存池 · central cache编写
在前文我们介绍了高并发内存池的整体框架,并且编写了thread cache部分,本文续高并发核心框架开始编写第二层框架,即centralcache。
_lazy
2025/03/11
1150
高并发内存池 · central cache编写
【项目日记】高并发内存池---实现线程缓存
我们需要实现的是一个这样的效果:线程缓存(256KB)中每个空间位置映射到在哈希表上,对应一个自由链表,申请空间时从自由链表中取出一个对象,没有就去中心缓存进行申请!
叫我龙翔
2024/09/06
1020
【项目日记】高并发内存池---实现线程缓存
GO进阶(4) 深入Go的内存管理
       Go语言成为高生产力语言的原因之一自己管理内存:Go抛弃了C/C++中的开发者管理内存的方式,实现了主动申请与主动释放管理,增加了逃逸分析和GC,将开发者从内存管理中释放出来,让开发者有更多的精力去关注软件设计,而不是底层的内存问题。
黄规速
2023/02/27
6710
GO进阶(4) 深入Go的内存管理
一文搞懂Go1.20内存分配器
关于Go内存分配器的分析文章很多,看到的比较经典的有刘丹冰Aceld的一站式Golang内存管理洗髓经,最近学习了该篇文章和其他相关文章,结合Go1.20最新的源码,复习了下Go内存分配的知识,输出了自己的学习笔记。要学习Go GC实现,需要先搞定内存分配,内存分配是GC垃圾回收的前传。
涂明光
2024/03/10
1K0
Go内存管理-上篇
本主题文章讲Go内存分配管理,分为上篇和下篇两篇文章,上篇主要讲内存分配相关概念和tcmalloc原理,下篇将具体介绍Go内存分配原理。这是上篇部分,核心内容在tcmalloc,之所以介绍tcmalloc是因为Go的内存分配算法来源于Google为C语言开发的tcmalloc(thread-caching malloc)算法。理解了tcmalloc算法,也就基本理解了Go的内存分配原理。
数据小冰
2022/08/15
7430
Go内存管理-上篇
高并发内存池 · 整体框架认识和thread cache编写
在第一篇文章中,我们介绍了内存池的基本概念,引入了新话题,内存碎片,在内存碎片这个话题中,我们了解到了有两个概念,一个是外碎片,对于外碎片我们的理解是地址空间中因为容量太小,没法直接被分配的空间,另一个是内碎片,对于内碎片的问题在定长内存池我们暂时无法理解,但是在本节内容,我们可以对内碎片有一个非常形象的理解。
_lazy
2025/03/08
980
高并发内存池 · 整体框架认识和thread cache编写
相关推荐
使用基数树优化高并发内存池(替代加锁访问的哈希表和红黑树)
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验