正文7:内存池的分类、原理与优点及怎么手撕内存池(代码放github,未完待续10/4)

1.内存池分类

0)使用内存池的优点

①不用内存储不利于内存管理,不用内存存储池可以用单例类去分配
②不适用内存池的话,会有内存碎片
③使用了内存池利于检测内存泄漏
④节省小块内存

1)伙伴算法的分配,以一页大小4k举例

  • 分配原理

在这里插入图片描述
从8、16、32字节分配到2k字节

  • 使用场景

适合分配单位以页为分配单位,适合在物理内存上分配和回收,不使用在虚拟内存

2)slab算法

  • 分配原理

4K分成若干个4字节,若干个8字节,若干个16字节等等

  • 使用举例

例如stl的allocator

3)针对一个类的内存池演示代码

(1)介绍

来自王建伟《新经典C++》内存池

(2)基础代码部分
#include <iostream>
#include <ctime>
typedef long clock_t;


class A
{
public:
    static      void*   operator    new(size_t size);
    static      void    operator    delete(void* phead);   
    static      int     m_iCount;                           //用于统计分配次数统计,每new一次
    static      int     m_iMallocCount;                     //用于统计malloc次数,每malloc一次加1

private:
    A* next;
    static A* m_FreePosi;                                   //总是指向一块可以分配出去的内存的首地址
    static int m_sTrunkCount;                               //一次分配多少倍该类的内存
};

void* A::operator new(size_t size)
{
    //不再用传统方式实现,而是用内存池实现
    
    //传统方式
    //A* ppoint = (A*)malloc(size);
    //return ppoint;

    //1.初始化内存池
    A* tmplink;
    if(m_FreePosi == nullptr)
    {
        //2.为空,我们要申请一大块内存
        size_t realsize = m_sTrunkCount*size;               //要申请m_sTrunkCount这么多的内存
        m_FreePosi = reinterpret_cast<A*>(new char[realsize]);//传统new,调用底层传统malloc

        tmplink = m_FreePosi;

        //3.把分配的内存链接起来
        for(;tmplink !=&m_FreePosi[m_sTrunkCount - 1]; ++tmplink)
        {
            tmplink->next = tmplink + 1;
        }
        tmplink->next = nullptr;
        ++m_iMallocCount;

    }

    //4.归位
    tmplink = m_FreePosi;
    m_FreePosi = m_FreePosi->next;//m_FreePosi总是指向能分配内存的下一块首地址
    ++m_iCount;
    return tmplink;
}

void  A::operator delete(void* phead)
{
    //1.传统
    //free(phead);

    //2.内存池做法
    (static_cast<A*>(phead)->next) = m_FreePosi;
    m_FreePosi = static_cast<A*>(phead);

}

int A::m_iCount = 0;
int A::m_iMallocCount = 0;
A* A::m_FreePosi = nullptr;
int A::m_sTrunkCount = 500;

//主要用来测试时间
int main()
{

    //和时间有关的类型

    clock_t start,end;
    start = clock();
    for(int i = 0; i< 5000000;++i)
    {
        A* pa = new A();
    }
    end = clock();

    std::cout<<"申请分配的内存次数为:"<<A::m_iCount<<std::endl;
    std::cout<<"实际malloc的次数为:"<<A::m_iMallocCount<<std::endl;
    std::cout<<"用时(毫秒):"<<end-start<<std::endl;


    return 0;
}


(3)内存池对于速度的比较
  • 设置A::m_sTrunkCount = 5
root@iZbp1do67v9l7zwkcs2b22Z:~/code/memoryPool# g++ versionWang.cpp 
root@iZbp1do67v9l7zwkcs2b22Z:~/code/memoryPool# ./a.out 
申请分配的内存次数为:5000000
实际malloc的次数为:1000000
用时(毫秒)78767
Hello world
  • 设置A::m_sTrunkCount = 500
root@iZbp1do67v9l7zwkcs2b22Z:~/code/memoryPool# g++ versionWang.cpp 
root@iZbp1do67v9l7zwkcs2b22Z:~/code/memoryPool# ./a.out 
申请分配的内存次数为:5000000
实际malloc的次数为:10000
用时(毫秒)53223
  • 纯malloc
root@iZbp1do67v9l7zwkcs2b22Z:~/code/memoryPool# g++ versionWang.cpp 
root@iZbp1do67v9l7zwkcs2b22Z:~/code/memoryPool# ./a.out 
申请分配的内存次数为:0
实际malloc的次数为:0
用时(毫秒)187964
  • 结论

可以看出一次分配500块内存虽然比一次分配5块内存节省分配次数,分配500W次大概少了99W次,但是时间上只相差了25000ms,用malloc的时间更多

(4)基础代码增加嵌入式指针(目的:节省4字节的next内存)
#include <iostream>
#include <ctime>
typedef long clock_t;

class myallocator
{
public:
    //分配内存接口
    void* allocate(size_t size)
    {
        obj* tmplink;
        if(m_FreePosi == nullptr)
        {
            //为空,我要申请空间,要申请一大块内存
            size_t realsize = m_sTrunkCout * size;//申请m_sTrunkCout这么多倍的内存
            m_FreePosi = (obj*)malloc(realsize);
            tmplink = m_FreePosi;

            //把分配出来的这一块内存(5小块)彼此链接起来,供后续使用
            for(int i = 0 ;i < m_sTrunkCout - 1; ++i)
            {
                tmplink->next = (obj*)((char*)tmplink + size);
                tmplink = tmplink->next;
            }//end for
            tmplink->next = nullptr;
        }//end if
        tmplink = m_FreePosi;
        m_FreePosi = m_FreePosi->next;
        return tmplink;
    }

    void dellocate(void* phead)
    {
        ((obj*)phead)->next = m_FreePosi;
        m_FreePosi = (obj*)phead;
    }

private:
    //写在类里面的结构
    struct obj
    {
        struct obj* next;                   //这个next就是一个嵌入式指针
    };
    int m_sTrunkCout = 5;                   //一次分配5倍的该类内存作为内存池的大小
    obj* m_FreePosi = nullptr;

};

class A
{
public:
    //必须保证sizeof(A)凑够4字节,这里两个int成员8字节了,所以使用类myallocator毫无问题
    int m_i;
    int m_j;
public:
    static myallocator myalloc;             //静态成员变量,跟着类A走

    static void* operator new(size_t size)
    {
        return myalloc.allocate(size);
    }

    static void operator delete(void* phead)
    {   
        return myalloc.dellocate(phead);
    }

};

myallocator A::myalloc;                     //在类A之外定义一下这个静态成员变量

int main()
{
    A* mypa[100];
    for(int i = 0;i< 15;++i)
    {
        mypa[i] = new A();
        printf("%p\n",mypa[i]);
    }
    for(int i = 0;i<15;++i)
    {
        delete mypa[i];
    }
    return 0;
}

每个对象间隔8字节

4)仿nginx代码(客户端断开,释放分配的内存)

5)类模板,用链表做对象池

  • assertx.h


// assertx.h - simple assertion functionality.
//
// By Paul Glinker
//

#ifndef ASSERTX_H
#define ASSERTX_H


#ifdef NDEBUG


#define ASSERT( exp )


#else // !def NDEBUG


#ifdef _WINDOWS

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>

inline void assert_windows(const char *szExp, const char *szFilename, int iLineNum)
{
	char szMsg[128];
	
	sprintf(szMsg, "Expr: (%s)\nFile: %s\nLine: %d\n", szExp, szFilename, iLineNum);

	MessageBox(NULL,szMsg,"ASSERTION FAILED!", (MB_OK|MB_ICONERROR));

	exit(1);
}

#define ASSERT( exp )	if ( !(exp) ) assert_windows( #exp, __FILE__, __LINE__ )


#else // !def _WINDOWS


#include <stdio.h>
#include <stdlib.h>

inline void assert_generic(const char *szExp, const char *szFilename, int iLineNum)
{
	printf("================================================\n");
	printf("ASSERTION FAILED!\nExpr: (%s)\nFile: %s\nLine: %d\n", szExp, szFilename, iLineNum);
	printf("================================================\n");

	exit(1);
}

#define ASSERT( exp )	if ( !(exp) ) assert_generic( #exp, __FILE__, __LINE__ )


#endif //_WINDOWS

#endif // NDEBUG


#endif // ASSERTX_H
  • List.h
// TFreeList.h - a templated freelist class.
//
// By Paul Glinker
//

#ifndef TFreeList_h
#define TFreeList_h


#include "assertx.h"

template <class FLDataType>
class TFreeList
{
public:

	// Construct a TFreeList with the specified number
	// of objects available for allocation.
	TFreeList(int iNumObjects)
	{
		ASSERT( (iNumObjects > 0) && "Invalid TFreeList size specified." );

		m_pObjectData = new FLDataType[iNumObjects];
		m_ppFreeObjects = new FLDataType*[iNumObjects];

		ASSERT( m_pObjectData && "Not enough memory to allocate object data!" );
		ASSERT( m_ppFreeObjects && "Not enough memory to allocate pointer stack!" );

		m_iNumObjects = iNumObjects;
		m_bFreeOnDestroy = true;

		FreeAll();
	}

	// Constructs a TFreeList with the specified number
	// of objects available for allocation from pre-allocated
	// memory referenced by "pObjectData" and "ppFreeObjects".
	// Note that pObjectData and ppFreeObjects must have
	// "iNumObjects" elements each.
	TFreeList(FLDataType *pObjectData, FLDataType **ppFreeObjects, int iNumObjects)
	{
		ASSERT( (iNumObjects > 0) && "Invalid TFreeList size specified." );

		m_pObjectData = pObjectData;
		m_ppFreeObjects = ppFreeObjects;

		ASSERT( m_pObjectData && "A NULL pointer was passed for the object data!" );
		ASSERT( m_ppFreeObjects && "A NULL pointer was passed for the pointer stack!" );

		m_iNumObjects = iNumObjects;
		m_bFreeOnDestroy = false;

		FreeAll();
	}

	~TFreeList(void)
	{
		// If we have allocated memory,
		// then we must free it.
		if (m_bFreeOnDestroy)
		{
			delete [] m_ppFreeObjects;
			delete [] m_pObjectData;
		}
	}

	// Returns a pointer to a free instance of FLDataType.
	FLDataType *NewInstance(void)
	{
		ASSERT( m_iTop && "Tried to get a new instance but there"
						  "were no free instances available for "
						  "allocation. Consider using GetFree()!" );
			
		return m_ppFreeObjects[--m_iTop];
	}

	// Reclaims the instance referenced by pInstance.
	void FreeInstance(FLDataType *pInstance)
	{
		ASSERT( (pInstance >= &(m_pObjectData[0])) &&
				(pInstance <= &(m_pObjectData[m_iNumObjects-1])) &&
				"Tried to free an object that was"
				"not from this TFreeList" );

		// You might consider putting a debug-only check here to make sure
		// the instance that is being freed isn't already free.

		ASSERT( (m_iTop < m_iNumObjects) && "You have freed at least one"
											"instance more than once." );

		m_ppFreeObjects[m_iTop++] = pInstance;
	}

	// Makes all instances available for allocation.
	void FreeAll(void)
	{
		int iIndex = (m_iNumObjects-1);

		for (m_iTop = 0; m_iTop < m_iNumObjects; m_iTop++)
		{
			m_ppFreeObjects[m_iTop] = &(m_pObjectData[iIndex--]);
		}		
	}

	// Returns the total number of objects
	// managed by this TFreeList.
	int GetSize(void)
	{
		return m_iNumObjects;
	}

	// Returns the number of instances available
	// for allocation.
	int GetFree(void)
	{
		return m_iTop;
	}

private:

	// Points to the array of objects.
	FLDataType *m_pObjectData;

	// The number of objects in m_pObjectData.
	int m_iNumObjects;


	// Points to an array of pointers to free
	// objects within m_pObjectData.  Used as
	// a fixed sized stack.
	FLDataType **m_ppFreeObjects;

	// Keeps track of the first available object in
	// m_ppFreeObjects (the top of the stack).
	int m_iTop;


	// Remembers weather or not to free memory on
	// destruction.
	bool m_bFreeOnDestroy;
};
#endif // TFreeList_h

6)类模板,用红黑树当对象池

2.内存池原理及特点

3.内存池代码讲解

4.代码传送门

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
这是一门linux下c++通讯架构实战课程,针对c/c++语言已经掌握的很熟并希望进一步深造以将来用c++在linux下从事网络通讯领域/网络服务器的开发和架构工作。这门课程学习难度颇高但也有着极其优渥的薪水(最少30K月薪,最高可达60-80K月薪),这门课程,会先从nginx源码的分析和讲解开始,逐步开始书写属于自己的高性能服务器框架代码,完善个人代码库,这些,将会是您日后能取得高薪的重要筹码。本课程原计划带着大家逐行写代码,但因为代码实在过于复杂和精细,带着写代码可能会造成每节课至少要4~5小时的超长时间,所以老师会在课前先写好代码,主要的时间花费在逐行讲解这些代码上,这一点望同学们周知。如果你觉得非要老师领着写代码才行的话,老师会觉得你当前可能学习本门课程会比较吃力,请不要购买本课程,以免听不懂课程并给老师差评,差评也会非常影响老师课程的销售并造成其他同学的误解。 这门课程要求您具备下面的技能:(1)对c/c++语言掌握的非常熟练,语言本身已经不是继续学习的障碍,并不要求您一定熟悉网络或者linux;(2)对网络通讯架构领域有兴趣、勇于挑战这个高难度的开发领域并期望用大量的付出换取高薪;在这门课程中,实现了一个完整的项目,其中包括通讯框架和业务逻辑框架,浓缩总结起来包括如下几点:(1)项目本身是一个极完整的多线程高并发的服务器程序;(2)按照包头包体格式正确的接收客户端发送过来的数据包, 完美解决收包时的数据粘包问题;(3)根据收到的包的不同来执行不同的业务处理逻辑;(4)把业务处理产生的结果数据包正确返回给客户端;本项目用到的主要开发技术和特色包括:(1)epoll高并发通讯技术,用到的触发模式是epoll中的水平触发模式【LT】;(2)自己写了一套线程池来处理业务逻辑,调用适当的业务逻辑处理函数处理业务并返回给客户端处理结果;(3)线程之间的同步技术包括互斥量,信号量等等;(4)连接池中连接的延迟回收技术,这是整个项目中的精华技术,极大程度上消除诸多导致服务器程序工作不稳定的因素;(5)专门处理数据发送的一整套数据发送逻辑以及对应的发送线程;(6)其他次要技术,包括信号、日志打印、fork()子进程、守护进程等等;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值