STL空间配置器那点事

STL简介

  STL(Standard Template Library,标准模板库),从根本上说,STL是一些“容器”的集合,这些“容器”有list,vector,set,map等,STL也是算法和其他一些组件的集合。

谈及组件,那么我们就首先来简单谈下STL六大组件,其相关的设计模式使用,以及各组件之间的协作关系。

设计模式一览

六大组件简单介绍

1. 空间配置器:内存池实现小块内存分配,对应到设计模式--单例模式(工具类,提供服务,一个程序只需要一个空间配置器即可),享元模式(小块内存统一由内存池进行管理)

2.迭代器:迭代器模式,模板方法

3.容器:STL的核心之一,其他组件围绕容器进行工作:迭代器提供访问方式,空间配置器提供容器内存分配,算法对容器中数据进行处理,仿函数伪算法提供具体的策略,类型萃取  实现对自定义类型内部类型提取。保证算法覆盖性。其中涉及到的设计模式:组合模式(树形结构),门面模式(外部接口提供),适配器模式(stack,queue通过deque适配得  到),建造者模式(不同类型树的建立过程)。

4.类型萃取:基于范型编程的内部类型解析,通过typename获取。可以获取迭代器内部类型value_type,Poter,Reference等。

5.仿函数:一种类似于函数指针的可回调机制,用于算法中的决策处理。涉及:策略模式,模板方法。

6适配器:STL中的stack,queue通过双端队列deque适配实现,map,set通过RB-Tree适配实现。涉及适配器模式。

关于六大组件之间的具体关系如图简单描述

ps(图技术比较水,见谅,如有bug,请指正)

貌似扯的多了,来谈谈主题《空间配置器》问题吧。

STL空间配置器产生的缘由:

  在软件开发,程序设计中,我们不免因为程序需求,使用很多的小块内存(基本类型以及小内存的自定义类型)。在程序中动态申请,释放。

这个过程过程并不是一定能够控制好的,于是乎,

问题1:就出现了内存碎片问题。(ps外碎片问题)

问题2:一直在因为小块内存而进行内存申请,调用malloc,系统调用产生性能问题。

注:内碎片:因为内存对齐/访问效率(CPU取址次数)而产生 如 用户需要3字节,实际得到4或者8字节的问题,其中的碎片是浪费掉的。

  外碎片:系统中内存总量足够,但是不连续,所以无法分配给用户使用而产生的浪费。下边简单图解

这两个问题解释清楚之后,就来谈STL空间配置器的实现细节了

实现策略

  用户申请空间大于128?

  yes:调用一级空间配置器

  no:调用二级空间配置器

大致实现为:

  二级空间配置由内存池以及伙伴系统:自由链表组成

  一级空间配置器直接封装malloc,free进行处理,增加了C++中的set_handler机制(这里其实也就是个略显牵强的装饰/适配模式了),增加内存分配时客户端可选处理机制。

可配置性:

  客户端可以通过宏__USE_MALLOC进行自定义选择是否使用二级空间配置器。

一级空间配置器就主要封装malloc,添加handler机制了,这里就不罗嗦了,相信各位都是可以通过源码了解到的

关于二级空间配置器:

最后再罗嗦一点,说说Trace问题,然后就给出代码了。

Trace使用

  对于内存池的内部实现过程共还是比较复杂的,虽然代码量,函数比较简单。但是调用过程可能比较复杂。这时,如果我们选择debug调试,过程会相当的繁琐,需要仔细记录调用堆栈过程以及数据流向,逻辑变更等。对于楼主这种水货来说,估计完事就要苦了。

  所以,就使用Trace进行跟踪,打印数据流向,逻辑走向,文件,函数,方法,行位置。那么我们就能根据这个记录进行程序的排错以及调优了。

具体Trace简单如下

#pragma once

#define ___TRACE(...) fprintf(fout, "file[%s]line[%u]func[%s]::",__FILE__,__LINE__,__func__);\
fprintf(fout,__VA_ARGS__)

没错,就是这么简单,利用宏打印文件,行,函数位置,然后利用可变参数列表方式接收代码中具体位置的记录跟踪。

如下是代码摘取的Alloc中的跟中。

    static void *Allocate(size_t n)
    {
        ___TRACE("__MallocAllocTemplate to get  n = %u\n",n);
        void *result = malloc(n);
        if (0 == result)
        {
            result = OomMalloc(n);
        }
        return result;
    }

我的天:组织不好,手速太差,终于前奏完成。那么就给出空间配置器的代码了。

具体也可以在个人的github中获取源码

https://github.com/langya0/llhProjectFile/tree/master/STL

文件:Alloc.h

#pragma once

#include "Config.h"
__STLBEGIN
#include <memory.h>
#include <stdlib.h>

#define __THROW_BAD_ALLOC throw std::bad_alloc()

class __MallocAllocTemplate
{
private:
    static void *OomMalloc(size_t);
    static void *OomRealloc(void *, size_t);
    static void(*__malloc_alloc_oom_handler)();
public:
    static void *Allocate(size_t n)
    {
        ___TRACE("__MallocAllocTemplate to get  n = %u\n",n);
        void *result = malloc(n);
        if (0 == result)
        {
            result = OomMalloc(n);
        }
        return result;
    }
    static void *Reallocate(void *p, size_t old_sz, size_t new_sz)
    {
        void* result = realloc(p, new_sz);
        if (0 == result)
        {
            result = OomRealloc(p, new_sz);
            return result;
        }
    }

    static void Deallocate(void *p,size_t n)
    {
        ___TRACE("一级空间配置器释放 p= %p n = %u\n",p,n);
        free(p);
    }

    //
    static void(*set_malloc_handler(void(*f)()))()
    {
        ___TRACE("一级空间配置器,set Handler f = %p\n",f);
        void(*old)() = __malloc_alloc_oom_handler;
        __malloc_alloc_oom_handler = f;
        return (old);
    }

};

void *__MallocAllocTemplate::OomMalloc(size_t n)
{
    ___TRACE("一级空间配置器,不足进入Oo中n = %u\n",n);
    void(*my_malloc_handler)();
    void* result;
    for (;;)
    {
        my_malloc_handler = __malloc_alloc_oom_handler;
        if (0 == my_malloc_handler)
        {
            __THROW_BAD_ALLOC;
        }
        (*__malloc_alloc_oom_handler)();
        result = malloc(n);
        if (result)
            return result;
    }
}

void* __MallocAllocTemplate::OomRealloc(void* p, size_t n)
{
    void(*my_malloc_handler)();
    void* result;

    for (;;)
    {
        my_malloc_handler = __malloc_alloc_oom_handler;
        if (0 == my_malloc_handler)
        {
            __THROW_BAD_ALLOC;
        }
        (*my_malloc_handler)();
        result = realloc(p, n);
        if (result)
            return result;
    }
}
void(*__MallocAllocTemplate::__malloc_alloc_oom_handler)() = 0;

typedef __MallocAllocTemplate MallocAlloc;    //////这里放在#ifdef前边是因为二级空间配置器中还需要调用一级空间配置器
/////////////////////////////////////////根据是否配置__USE_ALLOC选择使用一级还是二级空间配置器
#ifdef __USE_ALLOC
typedef MallocAlloc Alloc;
#else //not define   __USE_ALLOC

template <bool threads, int inst>
class __DefaultAllocTemplate
{
protected:
    enum {_ALIGN = 8};
    enum {_MAX_BYTES = 128};
    enum {_NFREELISTS = 16}; // _MAX_BYTES/_ALIGN

    static size_t  RoundUp(size_t bytes)
    {
     return (((bytes) + (size_t) _ALIGN-1) & ~((size_t) _ALIGN - 1));
    }

protected:
    union _Obj {
        // 节点链接指针
    union _Obj* _freeListLink;
    //客户端数据
    char _ClientData[1];
    };

    //桶结构,保存链表
    static _Obj* _freeList[_NFREELISTS]; 

    //获取具体大小元素在表中的下标
    static  size_t _FreeListIndex(size_t __bytes)
    {
        return (((__bytes) + (size_t)_ALIGN-1)/(size_t)_ALIGN - 1);
    }

    static char* _start_free;
    static char* _end_free;
    static size_t _heap_size;
public:
    static void* Allocate(size_t n)
    {
        void * ret = 0;
        ___TRACE("二级空间配置器申请n = %u\n",n);
        if(n>_MAX_BYTES)
            ret = MallocAlloc::Allocate(n);

        _Obj* volatile * __my_free_list = _freeList + _FreeListIndex(n);

        _Obj* __result = *__my_free_list;
        if (__result == 0)
            ret = _Refill(RoundUp(n));
        else
        {
            *__my_free_list = __result -> _freeListLink;
            ret = __result;
        }
        return ret;
    }

    static void Deallocate(void* p, size_t n)
    {
        ___TRACE("二级空间配置器删除p = %p,n = %d\n",p,n);
        if (n > (size_t) _MAX_BYTES)
            MallocAlloc::Deallocate(p, n);
        else
        {
            _Obj* volatile*  __my_free_list = _freeList + _FreeListIndex(n);
            _Obj* q = (_Obj*)p;
            q -> _freeListLink = *__my_free_list;
        *__my_free_list = q;
        }
    }

    static void *Reallocate(void* p,size_t __old_sz,size_t __new_sz)
    {
        ___TRACE("二级空间配置器重新申请p = %p,new_sz = %d\n",p,__new_sz);
        void* __result;
        size_t __copy_sz;

        if (__old_sz > (size_t)_MAX_BYTES && __new_sz > (size_t)_MAX_BYTES)
        {
            return(realloc(p, __new_sz));
        }

        if (RoundUp(__old_sz) == RoundUp(__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);
    }
protected:
    static void *_Refill(size_t n)
    {
        ___TRACE("二级空间配置器自由链表填充n = %u\n",n);

        size_t nobjs = 20;
        void * ret;
        char * chunks = _ChunkAlloc(n,nobjs);

        if(nobjs == 1)
        {
            return chunks;
        }
        _Obj* volatile * __my_free_list = _freeList+_FreeListIndex(n);
        ret = chunks;

        for(size_t i = 1;i<nobjs;i++)
        {
            ((_Obj*)(chunks+n*i))->_freeListLink = *__my_free_list;
            *__my_free_list = (_Obj*)(chunks+n*i);
        }
        return ret;
    }

    /////////////这里的nobjs使用&,,内部需要复用逻辑,可能改变之
    static char * _ChunkAlloc(size_t n,size_t &nobjs)
    {
        size_t totalBytes = n*nobjs;
        size_t bytesLeft = _end_free - _start_free;

        if(bytesLeft>=totalBytes)
        {
            ___TRACE("二级空间配置器内存池填充n = %u,nobjs = %d\n",n,nobjs);
            _start_free += n*nobjs;
            return _start_free;
        }
        else if(bytesLeft>=n)
        {
            nobjs = (_end_free- _start_free)/n;
            ___TRACE("二级空间配置器内存池填充n = %u,nobjs = %d\n",n,nobjs);
            _start_free +=n*nobjs;
            return _start_free;
        }

        //bytesLeft [0,1)
        _Obj*  volatile * __my_free_list = _freeList + _FreeListIndex(bytesLeft);
        if(_start_free)
        {
            ___TRACE("二级空间配置器剩余bytesLeft = %u\n",bytesLeft);
            ((_Obj*)_start_free)->_freeListLink=*__my_free_list;
            *__my_free_list = (_Obj*)_start_free;
        }

        size_t bytesToGet = nobjs*n*2+(_heap_size>>4); 

        //malloc
        _start_free = (char*)malloc(bytesToGet);

        if(!_start_free)
        {
            ___TRACE("二级空间配置器malloc失败,在后续链表查找n = %d\n",n);
            for(size_t i = n + _ALIGN;i < _MAX_BYTES;i+=_ALIGN)
            {
                _Obj* volatile * cur = _freeList+_FreeListIndex(i);
                if(*cur)
                {
                    *cur = (*cur)->_freeListLink;
                    _start_free = (char*)*cur;
                    _end_free = _start_free + i;

                    return _ChunkAlloc(n,nobjs);
                }
            }

            ___TRACE("二级空间配置器:后续链表查找失败,转接一级配置,借用handler机制终止程序或者得到空间n = %d\n",n);
            _start_free = (char*)MallocAlloc::Allocate(n);
            return _ChunkAlloc(n,nobjs);
        }
        else
        {
            _end_free = _start_free + bytesToGet;
            _heap_size += bytesToGet;
            return _ChunkAlloc(n,nobjs);
        }

    }
};

template <bool __threads, int __inst>
char* __DefaultAllocTemplate<__threads, __inst>::_start_free= 0;

template <bool __threads, int __inst>
char* __DefaultAllocTemplate<__threads, __inst>::_end_free = 0;

template <bool __threads, int __inst>
size_t __DefaultAllocTemplate<__threads, __inst>::_heap_size = 0;

    // static _Obj* _freeList[_NFREELISTS];
template <bool threads, int inst>
typename __DefaultAllocTemplate<threads,inst>::_Obj*
__DefaultAllocTemplate<threads,inst>::_freeList[__DefaultAllocTemplate<threads,inst>::_NFREELISTS]
 = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

typedef __DefaultAllocTemplate<0,0>  Alloc;
#endif

__STLEND

void handler()
{
    cout << "here in  handler!\n"<<endl;
}
void test()
{
//    stl::Alloc::set_malloc_handler(handler);
    void *arr[21] = {0};
    ___TRACE("Clint to Get size = %u\n",5);

    // for(size_t i =0;i < 21;++i)
    //     arr[i] = stl::Alloc::Allocate(5);
    // for(size_t i =0;i < 21;++i)
    //     stl::Alloc::Deallocate(arr[i],5);
    arr[1] = stl::Alloc::Allocate(8);
    arr[2] = stl::Alloc::Allocate(72);

    arr[3] = stl::Alloc::Allocate(80);
}

文件:Trace.h

#pragma once

#define ___TRACE(...) fprintf(fout, "file[%s]line[%u]func[%s]::",__FILE__,__LINE__,__func__);\
fprintf(fout,__VA_ARGS__)

文件Config.h

#pragma once

/////模拟std实现宏方式开始,结束命名空间
namespace __STLNAMESPACE {}

#define __STLBEGIN namespace __STLNAMESPACE {

#define __STLEND }

namespace stl = __STLNAMESPACE;

//for  malloc
// #define __USE_MALLOC

///for  trace
#define __DEBUG
#ifdef __DEBUG
#include <stdio.h>
#define fout stdout    ///方便配置,修改Trace记录位置
#endif

终于大功告成,源码finished,那么,再给出测试结果截图了

最后,完成了主题工作之后。

再来说一些空间配置器的遗留问题吧:

1.仔细探究源码之后,你一定会发现一个问题,

  貌似二级空间配置器中的空间重头到尾都没看到他归还给系统。那么问题就是,内存池空间何时释放?

对于这个问题,在回头浏览一下源码及结构图,你就会发现

  大于128的内存,客户程序Deallocate之后会调free释放掉,归还给了系统。

  但是呢...............

  内存池中获取的空间,最终,假定用户都调用Dealloc释放调了,那么他们又在哪里呢?

    没有还给系统,没有在内存池,在自由链表中。

Got it:程序中不曾释放,只是在自由链表中,且配置器的所有方法,成员都是静态的,那么他们就是存放在静态区。释放时机就是程序结束。

2.如果需要释放,那么应该怎么处理呢?

  因为真正可以在程序运行中就归还系统的只有自由链表中的未使用值,但是他们并不一定是连续的(用户申请空间,释放空间顺序的不可控制性),所以想要在合适时间(eg一级配置器的handler中释放,或者设置各阀值,分配空间量到达时处理),就必须保证释放的空间要是连续的。保证连续的方案就是:跟踪分配释放过程,记录节点信心。释放时,仅释放连续的大块。

3.关于STL空间配置器的效率考究

  既然已经存在,而又被广泛使用,那么,整体的效率,以及和STL内部容器之间的使用配合还是没问题的。

我们考虑几种情况:

  a. 用户只需要无限的char类型空间,然而配置器中却对齐到8,于是乎,整个程序中就会有7/8的空间浪费。

  b.对于假定用户申请N次8空间,将系统资源耗到一定程度,然后全部释放了,自由链表中的空间都是连续的。却没有释放。

    但是:用户需要申请大于8的空间时,却依旧没有空间可用。

总之:这个问题就是,空间可能全部积攒在小块自由链表中,却没有用户可用的。这就尴尬了。

最后,关于配置器的其它问题,如果各位有什么新的思考,欢迎交流。邮箱:[email protected]

时间: 2024-10-14 01:10:57

STL空间配置器那点事的相关文章

stl空间配置器线程安全问题补充

摘要 在上一篇博客<STL空间配置器那点事>简单介绍了空间配置器的基本实现 两级空间配置器处理,一级相关细节问题,同时简单描述了STL各组件之间的关系以及设计到的设计模式等. 在最后,又关于STL空间配置的效率以及空间释放时机做了简单的探讨. 线程安全问题概述 为什么会有线程安全问题? 认真学过操作系统的同学应该都知道一个问题. first--进程是系统资源分配和调度的基本单位,是操作系统结构的基础,是一个程序的运行实体,同时也是一个程序执行中线程的容器 seconed--进程中作为资源分配基

STL空间配置器

1.什么是空间配置器? 空间配置器负责空间配置与管理.配置器是一个实现了动态空间配置.空间管理.空间释放的class template.以内存池方式实现小块内存管理分配.关于内存池概念可以点击:内存池. 2.STL空间配置器产生的缘由 在软件开发,程序设计中,我们不免因为程序需求,使用很多的小块内存(基本类型以及小内存的自定义类型).在程序中动态申请,释放.这个过程过程并不是一定能够控制好的,于是乎出现以下问题: 问题1:就出现了内存碎片问题.(ps外碎片问题) 问题2:一直在因为小块内存而进行

STL——空间配置器(SGI-STL)

一. 空间配置器标准接口 参见<STL源码剖析>第二章-2.1. 二.具备次配置力的SGI空间配置器 SGI STL的配置器与众不同,也与标准规范不同,其名称是alloc而非allocator,而且不接受任何参数(虽然SGI也定义有一个符合部分标准.名为sllocator的配置器,但SGI自己从未用过它,也不建议使用,主要因为效率不佳).这并不会带来什么困扰:我们通常很少需要自行指定配置器名称,而SGI STL的每一个容器都已经指定其缺省的空间配置器为alloc. // 在程序中要明白采用SG

stl 空间配置器理解

理解了一下stl的空间配置器,发现一个比较好的学习方法,跟着代码自己也跟着写一遍,顺便加些注释,可以更加帮助自己理解. 如new,delete一般,分为两个步骤,1,配置空间,2,构造对象(1,析构对象,2,释放空间) 一.构造和析构的基本工具(construct,destroy) 1,construct(构造) template<class T1,class T2> inline void construct(T1 * p,const T2 & value){ new (p) T1(

STL空间配置器(一)

STL空间适配器(一) Author:胡建 Time:2016/4/5 这是STL学习的第一部分,空间适配器,所谓空间适配器,就是用来管理内存的一个器具.对于STL来说,空间适配器是它可以正常工作的基础,也为它可以高效工作提供了动力.对于使用STL来说,它是不和用户直接打交道的,而是隐藏在一切STL组建之后,默默为各种内存申请提供支持的. 对于c++用户来说,new和delete很熟悉,这两个函数可以分别完成内存的申请和释放,和c里面的malloc和free如出一辙,SGI 有一个标准空间适配器

STL空间配置器、vector、list、deque、map复习

本文写于2017-03-03,从老账号迁移到本账号,原文地址:https://www.cnblogs.com/huangweiyang/p/6440830.html STL的六大组件:容器.算法.迭代器.空间配置器.容器适配器.仿函数. 空间配置器 空间配置器产生的缘由:由于程序需求,很多小块内存在程序中动态申请.释放.于是就容易出现内存外部碎片问题,同时由于一直调用malloc系统调用,产生性能问题. (注:内碎片:因为内存对齐/访问效率而差生如用户需要3字节,实际得到4字节的问题,其中的碎片

stl空间配置器alloc

SGI设计了双层级配置器,第一级配置器直接使用malloc()和free(),第二级配置器视情况采用不同的策略:当配置区块超过128bytes时,调用第一级配置器,当配置区块小于128bytes时,采用内存池方式 //SGI第一级配置器 template<int inst> class __malloc_alloc_template { private: //处理内存不足的情况 //c++ new handler机制:系统在内存配置需求无法被满足时,调用一个指定函数,参见effective c

STL——空间配置器

__malloc_alloc_template分配器:该分配器是对malloc.realloc以及free的封装: 当调用malloc和realloc申请不到内存空间的时候,会改调用oom_malloc()和oom_realloc(),这两个函数会反复调用用户传递过来的out of memory handler处理函数,直到能用malloc或者realloc申请到内存为止. 如果用户没有传递__malloc_alloc_oom_handler,__malloc_alloc_template会抛出

STL 之 空间配置器(allocator)

一.SGI 标准的空间配置器,std::allocator SGI也定义了一个符合部分标准,名为allocator的配置器,但是它自己不使用,也不建议我们使用,主要原因是效率不佳. 它只是把C++的操作符::operator new和::operator delete做了一层简单的封装而已. 二.SGI 特殊的空间配置器,std::alloc 由于SGI 标准的空间配置器只是把C++的操作符::operator new和::operator delete做了一层简单的封装,没有考虑到任何效率上的