STL源码之内存分配器alloc

内存池

Posted by chensong on 2020-06-25 14::38::06

前言

正文

一, C++中的对象new的流程

在C++中 new是调用::operator new (); //分配内存的

在调用赋值函数 construct();

释放的流程

destroy() ::operator delete ; //释放内存

		//初始化对象的值
    template <class T1, class T2>
    inline void construct(T1*p, const T2& value)
    {
        new (p) T1(value); // inivke construct
    }
	//调用析构函数
    template <class T>
    inline void destroy(T* pointer)
    {
        pointer->~T(); // inivke destroy();
    }

	template <int inst>
    class cnew_alloc {
    public:
        // this one is needed for proper simple_alloc wrapping
        typedef char value_type;
		//分配内存 
        static void*  allocate(size_t n)
        {
            return 0 == n ? 0 : ::operator new(n);
        }
		
        static void*  reallocate(void *p, size_t old_sz, size_t new_sz)
        {
            void* result = allocate(new_sz);
            size_t copy_sz = new_sz > old_sz? old_sz : new_sz;
            memcpy(result, p, copy_sz);
            deallocate(p, old_sz);
            return result;
        }
		//释放内存
        static void deallocate(void* p)
        {
            ::operator delete(p);
        }
        static void deallocate(void* p, size_t)
        {
            ::operator delete(p);
        }
    };

其实new 实际也是调用malloc 函数

二, STL中分配内存alloc

STL中有一级分配内存和二级分配内存

一级分配内存就是使用malloc分配内存的 当前申请的内存大于128个字节时就是使用malloc分配内存,当小于128个字节时就使用内存池,

1,一级内存就是简单对malloc进行封装

/***********************************************************************************************
	created: 		2020-06-24

	author:			chensong

	purpose:		cmalloc_alloc.h
我可能会遇到很多的人,听他们讲好2多的故事,我来写成故事或编成歌,用我学来的各种乐器演奏它。
然后还可能在一个国家遇到一个心仪我的姑娘,她可能会被我帅气的外表捕获,又会被我深邃的内涵吸引,在某个下雨的夜晚,她会全身淋透然后要在我狭小的住处换身上的湿衣服。
3小时候后她告诉我她其实是这个国家的公主,她愿意向父皇求婚。我不得已告诉她我是穿越而来的男主角,我始终要回到自己的世界。
然后我的身影慢慢消失,我看到她眼里的泪水,心里却没有任何痛苦,我才知道,原来我的心被丢掉了,我游历全世界的原因,就是要找回自己的本心。
于是我开始有意寻找各种各样失去心的人,我变成一块砖头,一颗树,一滴水,一朵白云,去听大家为什么会失去自己的本心。
我发现,刚出生的宝宝,本心还在,慢慢的,他们的本心就会消失,收到了各种黑暗之光的侵蚀。
从一次争论,到嫉妒和悲愤,还有委屈和痛苦,我看到一只只无形的手,把他们的本心扯碎,蒙蔽,偷走,再也回不到主人都身边。
我叫他本心猎手。他可能是和宇宙同在的级别 但是我并不害怕,我仔细回忆自己平淡的一生 寻找本心猎手的痕迹。
沿着自己的回忆,一个个的场景忽闪而过,最后发现,我的本心,在我写代码的时候,会回来。
安静,淡然,代码就是我的一切,写代码就是我本心回归的最好方式,我还没找到本心猎手,但我相信,顺着这个线索,我一定能顺藤摸瓜,把他揪出来。
************************************************************************************************/

//

#ifndef _CMALLOC_ALLOC_H_
#define _CMALLOC_ALLOC_H_

#include <cstdlib>
#include <cstdio>
namespace chen
{
    typedef void (* coom_handler_type)();

    template <int inst>
    class cmalloc_alloc
    {
    private:
        static void *oom_malloc(size_t);
        static void *oom_realloc(void *, size_t);
        static coom_handler_type oom_handler;
    public:
// this one is needed for proper simple_alloc wrapping
        typedef char value_type;

        static void * allocate(size_t n)
        {
            printf("alloc size = %llu\n", n);
            void *result = ::malloc(n);
            if (0 == result) result = oom_malloc(n);
            return result;
        }

        static void deallocate(void *p, size_t n /* n */)
        {
            printf("free size = %llu\n", n);
            ::free((char*)p);
        }

        static void * reallocate(void *p, size_t /* old_sz */, size_t new_sz)
        {
            void * result = ::realloc((char*)p, new_sz);
            if (0 == result) result = oom_realloc(p, new_sz);
            return result;
        }

        static coom_handler_type set_malloc_handler(coom_handler_type f)
        {
            coom_handler_type old = oom_handler;
            oom_handler = f;
            return(old);
        }
    };

    template <int inst>
    coom_handler_type cmalloc_alloc<inst>::oom_handler=(coom_handler_type)0 ;

    template <int inst>
    void * cmalloc_alloc<inst>::oom_malloc(size_t n)
    {
        coom_handler_type my_malloc_handler;
        void *result;

        for (;;)
        {
            my_malloc_handler = oom_handler;
            if (0 == my_malloc_handler)
            {
                fprintf(stderr,"out of memory");
                exit(1);
            }
            (*my_malloc_handler)();
            result = malloc(n);
            if (result)
            {
                return(result);
            }
        }
    }

    template <int inst>
    void * cmalloc_alloc<inst>::oom_realloc(void *p, size_t n)
    {
        coom_handler_type my_malloc_handler;
        void *result;

        for (;;)
        {
            my_malloc_handler = oom_handler;
            if (0 == my_malloc_handler)
            {
                fprintf(stderr,"out of memory");
                exit(1);
            }
            (*my_malloc_handler)();
            result = ::realloc((char*)p, n);
            if (result)
            {
                return(result);
            }
        }
    }
    typedef cmalloc_alloc<0 > malloc_alloc;
} // namespace chen
#endif //_CMALLOC_ALLOC_H_

2, 二级分配使用内存池技术

在内存池中维护16个内存块链表和还有被内存块使用备用内存

16数组中的内存块分别是 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 96, 104, 112, 120, 128 都是8倍数 内存对齐

中使用 &~ 运算符 是对需要内存进行内存调整到 8的倍数

例子:

31 ===>  32 //调整后是

0001 0111  ===>   (0001 0111 + 0111) & ~(0111) ===> 0010 0000 == 32

公式

static size_t round_up(size_t bytes)
{
	return (bytes + (7)) &~(7);
}

16个数组是维持16种内存块的链表

但是它分配内存块40k内存节点分配20个 是 800k是给数组中 中下标4中链表管理19个节点40k的内存 , 但是实际内存内存大于800k而是申请的1600k 还有的800k留给内存池管理了 // 当你在申请内存 32k的实际就会到内存池中拿出640k出现给数组下标3管理管理,这个就是STL的设置内存池精妙之处

/***********************************************************************************************
	created: 		2020-06-23

	author:			chensong

	purpose:		calloc_mem_pool
我可能会遇到很多的人,听他们讲好2多的故事,我来写成故事或编成歌,用我学来的各种乐器演奏它。
然后还可能在一个国家遇到一个心仪我的姑娘,她可能会被我帅气的外表捕获,又会被我深邃的内涵吸引,在某个下雨的夜晚,她会全身淋透然后要在我狭小的住处换身上的湿衣服。
3小时候后她告诉我她其实是这个国家的公主,她愿意向父皇求婚。我不得已告诉她我是穿越而来的男主角,我始终要回到自己的世界。
然后我的身影慢慢消失,我看到她眼里的泪水,心里却没有任何痛苦,我才知道,原来我的心被丢掉了,我游历全世界的原因,就是要找回自己的本心。
于是我开始有意寻找各种各样失去心的人,我变成一块砖头,一颗树,一滴水,一朵白云,去听大家为什么会失去自己的本心。
我发现,刚出生的宝宝,本心还在,慢慢的,他们的本心就会消失,收到了各种黑暗之光的侵蚀。
从一次争论,到嫉妒和悲愤,还有委屈和痛苦,我看到一只只无形的手,把他们的本心扯碎,蒙蔽,偷走,再也回不到主人都身边。
我叫他本心猎手。他可能是和宇宙同在的级别 但是我并不害怕,我仔细回忆自己平淡的一生 寻找本心猎手的痕迹。
沿着自己的回忆,一个个的场景忽闪而过,最后发现,我的本心,在我写代码的时候,会回来。
安静,淡然,代码就是我的一切,写代码就是我本心回归的最好方式,我还没找到本心猎手,但我相信,顺着这个线索,我一定能顺藤摸瓜,把他揪出来。
************************************************************************************************/

#ifndef CSTL_SOURCE_CALLOC_MEM_POOL_H
#define CSTL_SOURCE_CALLOC_MEM_POOL_H

#include "cmalloc_alloc.h"
#include <mutex>
namespace chen {

    union cnode_alloc_obj;
    union cnode_alloc_obj
    {
        union cnode_alloc_obj* free_list_link;
        char client_data[1]; // info address 
    };


    template <bool trheads, int inst>
    class calloc
    {
    public:
        // this one is needed for proper simple_alloc wrapping
        typedef char value_type;
        typedef cnode_alloc_obj obj;
    public:

        //内存对齐最小字节
        enum {__ALIGN = 8};
        // 内存池中最大内存节点 128
        enum {__MAX_BYTES = 128};
        // 内存
        enum {__NFREELISTS = __MAX_BYTES/__ALIGN};
    private:
        // 内存字节对齐 8的倍数
        static size_t  round_up(size_t bytes)
        {
            return ((bytes + __ALIGN -1) & ~(__ALIGN -1));
        }
        // 内存字节数组的下标
        static size_t free_list_index(size_t bytes)
        {
            return ((bytes + __ALIGN -1)/__ALIGN -1);//
        }
        //
        static void *refill(size_t n);

        static char * chunk_alloc(size_t size, int & nobjes);

        // Chunk allocation state.
        static char *start_free;
        static char *end_free;
        static size_t heap_size;
    public:
        /* n must be > 0      */
        static void * allocate(size_t n);
        /* p may not be 0 */
        static void deallocate(void *p, size_t n);
        static void * reallocate(void *p, size_t old_sz, size_t new_sz);
        static void show_info()
        {
            printf("mem_pool node size = %llu\n", (size_t)(end_free - start_free));
            printf("mem_pool use size = %llu\n", m_use_size);
            printf("mem_pool size = %llu\n", heap_size);
        }
    private :
        static obj *  free_list[__NFREELISTS];
        static  std::mutex m_lock;
        static size_t  m_use_size;
    };



    typedef calloc<false, 0 > csingle_client_alloc;
    typedef calloc<true, 0 >  cmultithreaded_alloc;


    template <bool threads, int inst>
    inline void * calloc<threads,inst>::allocate(size_t n)
    {
        obj **  my_free_list;
        obj *  result;

        m_use_size +=n;
        if (n > (size_t)__MAX_BYTES)
        {
            return(malloc_alloc::allocate(n));
        }
        printf("allocate index = %llu\n", free_list_index(n));
        my_free_list = free_list + free_list_index(n);
        // Acquire the lock here with a constructor call.
        // This ensures that it is released in exit or during stack
        // unwinding.
        /*REFERENCED*/
        if (threads)
        {
            m_lock.lock();
        }
        result = *my_free_list;
        if (result == 0)
        {
            void *r = refill(round_up(n));
            return r;
        }
        *my_free_list = result -> free_list_link;
        if (threads)
        {
            m_lock.unlock();
        }
        return (result);
    }
    template <bool threads, int inst>
    inline void calloc<threads, inst>::deallocate(void *p, size_t n)
    {
        obj *q = (obj *)p;
        obj  ** my_free_list;

        m_use_size -=n;
        if (n > (size_t)__MAX_BYTES)
        {

            malloc_alloc::deallocate(p, n);
            return;
        }
        printf("deallocate  index = %llu\n", free_list_index(n));
        my_free_list = free_list + free_list_index(n);
        // acquire lock

        if (threads)
        {
            m_lock.lock();
        }
        q->free_list_link = *my_free_list;
        *my_free_list = q;
        // lock is released here
        if (threads)
        {
            m_lock.unlock();
        }
    }




/* We allocate memory in large chunks in order to avoid fragmenting     */
/* the malloc heap too much.                                            */
/* We assume that size is properly aligned.                             */
/* We hold the allocation lock.                                         */
    template <bool threads, int inst>
    char* calloc<threads, inst>::chunk_alloc(size_t size, int& nobjs)
    {
        char * result;
        size_t total_bytes = size * nobjs;
        size_t bytes_left = end_free - start_free;

        if (bytes_left >= total_bytes)
        {
            result = start_free;
            start_free += total_bytes;
            return(result);
        }
        else if (bytes_left >= size)
        {
            nobjs = bytes_left/size;
            total_bytes = size * nobjs;
            result = start_free;
            start_free += total_bytes;
            return(result);
        }
        else
            {
                // 这边 heap_size  >> 4 ====> 扩容作准备的  内存池
            size_t bytes_to_get = 2 * total_bytes + round_up(heap_size >> 4);
            // Try to make use of the left-over piece.
            if (bytes_left > 0)
            {
                obj  ** my_free_list = free_list + free_list_index(bytes_left);

                ((obj *)start_free) -> free_list_link = *my_free_list;
                *my_free_list = (obj *)start_free;
            }
            start_free = (char *)malloc_alloc::allocate(bytes_to_get);
            if (0 == start_free)
            {
                int i;
                obj  ** my_free_list;
                obj *p;
                // Try to make do with what we have.  That can't
                // hurt.  We do not try smaller requests, since that tends
                // to result in disaster on multi-process machines.
                for (i = size; i <= __MAX_BYTES; i += __ALIGN)
                {
                    my_free_list = free_list + free_list_index(i);
                    p = *my_free_list;
                    if (0 != p)
                    {
                        *my_free_list = p -> free_list_link;
                        start_free = (char *)p;
                        end_free = start_free + i;
                        return(chunk_alloc(size, nobjs));
                        // Any leftover piece will eventually make it to the
                        // right free list.
                    }
                }
                end_free = 0;	// In case of exception.
                start_free = (char *)malloc_alloc::allocate(bytes_to_get);
                // This should either throw an
                // exception or remedy the situation.  Thus we assume it
                // succeeded.
            }
            heap_size += bytes_to_get;
            end_free = start_free + bytes_to_get;
            return(chunk_alloc(size, nobjs));
        }
    }

/* Returns an object of size n, and optionally adds to size n free list.*/
/* We assume that n is properly aligned.                                */
/* We hold the allocation lock.                                         */
    template <bool threads, int inst>
    void* calloc<threads, inst>::refill(size_t n)
    {
        int nobjs = 20;
        char * chunk = chunk_alloc(n, nobjs);
        obj  *  * my_free_list;
        obj * result;
        obj * current_obj, * next_obj;
        int i;

        if (1 == nobjs)
        {
            return(chunk);
        }
        my_free_list = free_list + free_list_index(n);

        /* Build free list in chunk */
        // node list -->>>>>>  8 * ? = 128
        result = (obj *)chunk;
        *my_free_list = next_obj = (obj *)(chunk + n);
        for (i = 1; ; i++)
        {
            current_obj = next_obj;
            next_obj = (obj *)((char *)next_obj + n);
            if (nobjs - 1 == i)
            {
                current_obj -> free_list_link = 0;
                break;
            }
            else
                {
                current_obj -> free_list_link = next_obj;
            }
        }
        return(result);
    }

    template <bool threads, int inst>
    void* calloc<threads, inst>::reallocate(void *p, size_t old_sz, size_t new_sz)
    {
        void * result;
        size_t copy_sz;

        if (old_sz > (size_t) __MAX_BYTES && new_sz > (size_t) __MAX_BYTES)
        {
            return(malloc_alloc::reallocate(p, old_sz, new_sz));
        }
        if (round_up(old_sz) == round_up(new_sz))
        {
            return(p);
        }
        result = allocate(new_sz);
        copy_sz = new_sz > old_sz? old_sz : new_sz;
        ::memcpy(result, p, copy_sz);
        deallocate(p, old_sz);
        return(result);
    }

    template <bool threads, int inst>
    char *calloc<threads, inst>::start_free = 0;

    template <bool threads, int inst>
    char *calloc<threads, inst>::end_free = 0;

    template <bool threads, int inst>
    size_t calloc<threads, inst>::heap_size = 0;
    template <bool threads, int inst>
    size_t calloc<threads, inst>::m_use_size = 0;
    template <bool threads, int inst>
    cnode_alloc_obj * calloc<threads, inst>::free_list[__NFREELISTS] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

}
#endif //CSTL_SOURCE_CALLOC_MEM_POOL_H


STL的内存分配测试案例地址链接

结语