`
tempsitegoogle
  • 浏览: 864954 次
文章分类
社区版块
存档分类
最新评论

STL源码剖析---空间配置器

 
阅读更多
看过STL空间配置器的源码,总结一下:
1、STL空间配置器:主要分三个文件实现,stl_construct.h 这里定义了全局函数construct()和destroy(),负责对象的构造和析构。stl_alloc.h文件中定义了一、二两级配置器,彼此合作,配置器名为alloc. stl_uninitialized.h 这里定义了一些全局函数,用来填充(fill)或复制(copy)大块内存数据,他们也都隶属于STL标准规划。
在stl_alloc.h中定义了两级配置器,主要思想是申请大块内存池,小块内存直接从内存池中申请,当不够用时再申请新的内存池,还有就是大块内存直接申请。当申请空间大于128字节时调用第一级配置器,第一级配置器没有用operator::new和operator::delete来申请空间,而是直接调用malloc/free和realloc,并且实现了类似c++中new-handler的机制。所谓c++ new handler机制是,你可以要求系统在内存配置需求无法被满足时,调用一个指定的函数。换句话说,一旦::operator::new无法完成任务,在丢出std::bad_alloc异常状态之前,会先调用由客端指定的处理例程,该处理例程通常称为new-handler.new-handler解决内存做法有特定的模式。SGI第一级配置器的allocate()和realloc都是在调用malloc和realloc不成功后,改调用oom_malloc()和oom_realloc(),后两者都有内循环,不断调用"内存不足处理例程",期望在某次调用之后,获得足够的内存而圆满完成任务。但如果“内存不足处理例程“并未被客端设定,oom_malloc()和oom_realloc便调用_THROW_BAD_ALLOC, 丢出bad_alloc异常信息,或利用exit(1)硬生生中止程序。
在stl_alloc.h中定义的第二级配置器中,如果区块够大,超过128字节时,就移交给第一级配置器处理。当区块小于128字节时,则以内存池管理,此法又称为次层配置,每次配置一大块内存,并维护对应的自由链表(free-list)。下次若再有相同大小的内存需求,就直接从free-list中拔出。如果客端释还小额区块,就由配置器回收到free-lists中,另外,配置器除了负责配置,也负责回收。为了管理方便,SGI第二级配置器会主动将任何小额区块的内存需求量上调至8的倍数。并维护16个free-lists,各自管理大小分别为8,16,24,32,40,48,56,64,72,80,88,96,104, 112,120,128 字节的小额区块。当申请小于等于128字节时就会检查对应的free list,如果free-list中有可用的区块,就直接拿来,如果没有,就准备为对应的free-list 重新填充空间。新的空间将取自内存池,缺省取得20个新节点,如果内存池不足(还足以一个以上的节点),就返回的相应的节点数.如果当内存池中连一个节点大小都不够时,就申请新的内存池,大小为2*total_bytes+ROUND_UP(heap_size>>4),totoal_bytes 为申请的空间大小,ROUND_UP调整为8的倍数,heap_size为当前总申请内存池的大小。如果申请该内存池成功就把原来内存池中剩下的空间分配给适当的free-list.万一山穷水尽,整个system heap空间都不够了(以至无法为内存池注入源头活水),malloc()行动失败,就会四处寻找有无"尚有未用区块,且区块足够大 "之free lists.找到了就挖一块交出,找不到就调用第一级配置器。第一级配置器其实也是使用malloc来配置内存。但它有out-of-memory处理机制(类似new-handler机制),或许有机会释放其他的内存拿来此处使用。如果可以就成功,否则发出bad_alloc异常。
2、STL的默认内存分配器
隐藏在这些容器后的内存管理工作是通过STL提供的一个默认的allocator实现的。当然,用户也可以定制自己的allocator,只要实现allocator模板所定义的接口方法即可,然后通过将自定义的allocator作为模板参数传递给STL容器,创建一个使用自定义allocator的STL容器对象,如:
stl::vector<int, UserDefinedAllocator> array;
大多数情况下,STL默认的allocator就已经足够了。这个allocator是一个由两级分配器构成的内存管理器,当申请的内存大小大于128byte时,就启动第一级分配器通过malloc直接向系统的堆空间分配,如果申请的内存大小小于128byte时,就启动第二级分配器,从一个预先分配好的内存池中取一块内存交付给用户,这个内存池由16个不同大小(8的倍数,8~128byte)的空闲列表组成,allocator会根据申请内存的大小(将这个大小round up成8的倍数)从对应的空闲块列表取表头块给用户。
这种做法有两个优点:
(1)小对象的快速分配。小对象是从内存池分配的,这个内存池是系统调用一次malloc分配一块足够大的区域给程序备用,当内存池耗尽时再向系统申请一块新的区域,整个过程类似于批发和零售,起先是由allocator向总经商批发一定量的货物,然后零售给用户,与每次都总经商要一个货物再零售给用户的过程相比,显然是快捷了。当然,这里的一个问题时,内存池会带来一些内存的浪费,比如当只需分配一个小对象时,为了这个小对象可能要申请一大块的内存池,但这个浪费还是值得的,况且这种情况在实际应用中也并不多见。
(2)避免了内存碎片的生成。程序中的小对象的分配极易造成内存碎片,给操作系统的内存管理带来了很大压力,系统中碎片的增多不但会影响内存分配的速度,而且会极大地降低内存的利用率。以内存池组织小对象的内存,从系统的角度看,只是一大块内存池,看不到小对象内存的分配和释放。
实现时,allocator需要维护一个存储16个空闲块列表表头的数组free_list,数组元素i是一个指向块大小为8*(i+1)字节的空闲块列表的表头,一个指向内存池起始地址的指针start_free和一个指向结束地址的指针end_free。空闲块列表节点的结构如下:
union obj
{
	union obj * free_list_link;
	char client_data[1];
};
这个结构可以看做是从一个内存块中抠出4个字节大小来,当这个内存块空闲时,它存储了下个空闲块,当这个内存块交付给用户时,它存储的时用户的数据。因此,allocator中的空闲块链表可以表示成:
obj* free_list[16];
3、分配算法

// 算法:allocate
// 输入:申请内存的大小size
// 输出:若分配成功,则返回一个内存的地址,否则返回NULL
{
	if(size 大于 128)
		启动第一级分配器直接调用malloc分配所需的内存并返回内存地址;
	else
	{
		将size向上round up成8的倍数并根据大小从free_list中取对应的表头free_list_head
		if(free_list_head 不为空)
		{
			从该列表中取下第一个空闲块并调整free_list,返回free_list_head
		}
		else
		{
			调用refill算法建立空闲块列表并返回所需的内存地址
		}
	}
}


// 算法:refill
// 输入:内存块的大小size
// 输出:建立空闲块链表并返回第一个可用的内存地址
{
	调用chunk_alloc算法分配若干个大小为size的连续内存区域并返回起始地址chunk和成功分配的块数nobj
	if(块数为1)
		直接返回 chunk;
	else
	{
		开始在chunk地址块中建立free_list
		根据size取free_list中对应的表头元素free_list_head 
		将free_list_head 指向chunk中偏移起始地址为size的地址处,即free_list_head = (obj*)(chunk+size)
		再将整个chunk中剩下的nobj-1个内存块串联起来构成一个空闲列表
		返回chunk,即chunk中第一个空闲的内存块
	}
}


// 算法:chunk_alloc
// 输入:内存块的大小size,预分配的内存块数nobj(以引用传递)
// 输出:一块连续的内存区域的地址和该区域内可以容纳的内存块的块数
{
	计算总共所需的内存大小total_bytes
	if(内存池足以分配,即end_free-start_free >= total_bytes)
	{
		则更新start_free
		返回旧的start_free
	}
	else if(内存池不够分配nobj个内存块,但至少可以分配一个)
	{
		计算可以分配的内存块数并修改nobj
		更新start_free并返回原来的start_free
	}
	else     // 内存池连一个内存块都分配不了
	{
		先将内存池的内存块链入到对应的free_list中后
		调用malloc操作重新分配内存池,大小为2倍的total_bytes为附加量,start_free指向返回的内存地址
		if(分配不成功)
		{
			if(16个空闲列表中尚有空闲块)
				尝试将16个空闲列表中空闲块回收到内存池中再调用chunk_alloc(size,nobj)
			else
				调用第一级分配器尝试out of memory机制是否还有用
		}
		更新end_free为start_free+total_bytes,heap_size为2倍的total_bytes
		调用chunk_alloc(size,nobj)
	}
}


// 算法:deallocate
// 输入:需要释放的内存块地址p和大小size
{
	if(size 大于128字节)
		直接调用free(p)释放
	else
	{
		将size向上取8的倍数,并据此获取对应的空闲列表表头指针free_list_head
		调整free_list_head将p链入空闲列表块中
	}
}
假设这样一个场景,free_list[2]已经指向了大小为24字节的空闲块链表,如图1所示,当用户向allocator申请21字节大小的内存块时,allocaotr会首先检查free_list[2]并将free_list[2]所指的内存块分配给用户,然后将表头指向下一个可用的空闲块,如图2所示。注意,当内存块在链表上是,前4个字节是用作指向下一个空闲块,当分配给用户时,它是一块普通的内存区。

图1 某时刻allocator的状态

图2 分配24字节大小的内存块
4、小结
STL中的内存分配器实际上是基于空闲列表(free list)的分配策略,最主要的特点是通过组织16个空闲列表,对小对象的分配做了优化。
1)小对象的快速分配和释放。当一次性预先分配好一块固定大小的内存池后,对小于128字节的小块内存分配和释放的操作只是一些基本的指针操作,相比于直接调用malloc/free,开销小。
2)避免内存碎片的产生。零乱的内存碎片不仅会浪费内存空间,而且会给OS的内存管理造成压力。
3)尽可能最大化内存的利用率。当内存池尚有的空闲区域不足以分配所需的大小时,分配算法会将其链入到对应的空闲列表中,然后会尝试从空闲列表中寻找是否有合适大小的区域,
但是,这种内存分配器局限于STL容器中使用,并不适合一个通用的内存分配。因为它要求在释放一个内存块时,必须提供这个内存块的大小,以便确定回收到哪个free list中,而STL容器是知道它所需分配的对象大小的,比如上述:
stl::vector<int> array;
array是知道它需要分配的对象大小为sizeof(int)。一个通用的内存分配器是不需要知道待释放内存的大小的,类似于free(p)。





分享到:
评论

相关推荐

    STL源码剖析简体中文完整版(清晰扫描带目录).pdf

    第1 章 STL 概论与著作版本简介 第2 章 空间配置器(allocator) 第3 章 迭代器(iterators)概念与 traits 编程技法 第4 章 序列式容器(sequ ence containers) 第5 章 开关式容器(associated containers) 第6 ...

    STL源码剖析.pdg

    2.2 具备次配置力(sub-allocation)的sgi 空间配置器 047 2.2.1 sgi 标准的空间配置器,std::allocator 047 2.2.2 sgi 特殊的空间配置器,std::alloc 049 2.2.3 构造和析构基本工具:construct() 和 destroy() ...

    STL 源码剖析(侯捷先生译著)

    2.2 具备次配置力(sub-allocation)的SGI 空间配置器 047 2.2.1 SGI 标准的空间配置器,std::allocator 047 2.2.2 SGI 特殊的空间配置器,std::alloc 049 2.2.3 构造和析构基本工具:construct() 和 destroy() ...

    《STL源码剖析》(候捷 著)

    第2章 空间配置器(allocator) 第3章 迭代器(iterators)概念与traits编程技法 第4章 序列式容器(sequence containers) 第5章 关联式容器(associattive containers) 第6章 算法(algorithms) 第7章 仿函数...

    STL源码剖析完整版

    疱丁解牛(侯捷自序) 前言 第1章 STL概论与版本简介 第2章 空间配置器(allocator) 第3章 迭代器(iterators)概念与traits编程技法 第4章 序列式容器(sequence containers) 第5章 关联式容器(associattive ...

    STL源码剖析

    第2章 空间配置器(allocator) 第3章迭代器(iterators)概念与traits编程技法 第4章 序列式容器(sequence containers) 第5章 关联式容器(associattive containers) 第6章 算法(algorithms) 第7章 仿函数...

    STL:STL原始码分析

    整个模块准备对学习STL源码剖析之后做一个系统的STL源码剖析,这些都是我个人的理解,如果分析有什么问题欢迎各位大佬们指出。也很感谢作者以及网络中各个大佬的总结,让我也能更容易更深刻的理解到STL强大和方便,...

    my-mem-pool:剖析和注释SGI STL二级空间配置器源码与nginx内存池源码,并使用C ++ OOP进行仿写

    本仓库包含以下内容:注释过的SGI STL二级空间配置器源码以及进行的分析整理注释过的nginx内存池二进制以及进行的分析整理my_stl_allocator my_nginx_mem_pool目录背景在学习编程的过程中,一味的闭门造车是不可取的...

    MiniSTL:实现MiniSTL是STL的一个子集,适合用于学习《 STL原始码剖析》(有注释分析测试)

    MiniSTL根据《 STL源码剖析》实现简易的STL标准库的一个子集实现基本功能学习模板编程文件描述已完成空间配置器:接口simple_alloc,一级配置器allocator,二级配置器alloc构造:constructor析构:deatroy内存初始化...

    C++ 后台工程师面试宝典

    再硬核|5 千字长文+ 30 张图解 | 陪你手撕 STL 空间配置器源码 硬核|万字长文炸裂!手撕 STL 迭代器源码与 traits 编程技法 超硬核 | 2 万字+20 图带你手撕 STL 序列式容器源码 硬核来袭 | 2 万字 + 10 图带你手撕 ...

    leetcode和ctf-skr_learn_list:skr_learn_list

    c++和stl源码剖析(视频+代码) Prime c++:第一章到第六章 前面和prime c没什么太大的区别,就暂且不进行记录 stl源码剖析 STL是C++标准库的一部分,占据了大部分的比例。STL借助模板把常用的数据结构及其算法都...

    传智播客扫地僧视频讲义源码

    12_直接通过内存标号操作内存空间_课堂答疑 13_中午课程回顾 14_内存四区基本原理_全局区案例理解 15_内存四区_堆栈案例理解 16_课堂答疑_理解指针的关键关键在内存 17_vs20102013上配置#系列快捷方式 18_栈的属性和...

Global site tag (gtag.js) - Google Analytics