
点击蓝字关注我哦



▼《名企高频考点-C++如何避免vector的动态扩容,为何是2倍(下)》▼
ps:请在WiFi环境下打开,如果有钱任性请随意
01
前情回顾
上文中主要讨论了vector扩容机制、为什么选择以倍数方式扩容,为什么一般选择倍数小于2倍。由于STL并没有规定vector具体的扩容方式,选择以1.5倍方式还是以2倍方式扩容,我觉得与实现STL操作系统的堆内存管理方式有关系,因此各个版本的STL在实现时可能会考虑所在系统的堆管理方式,以下原因仅为个人见解,欢迎一起讨论。02
vs为什么选择1.5倍方式扩容
在windows中,已释放的或未被申请的堆空间使用堆表进行管理,最重要的堆表有两种:空闲双向链表FreeList(以下简称空表)和快速单向链表Lookaside(以下简称快表)。
空表
按照内存块大小的不同,空表总共被分为128条,free[1]~free[127]每个空表中管理的都是8的整数倍的堆内存块,逐次递增,将空闲堆块按照大小不同链入不同的空表中,来方便堆管理系统高效检索指定大小的堆空间。free[0]管理大于1024byte小于512K的堆块,这些堆块按照大小在free[0]中依次排列下去。
快表
快表是windows用来加速堆块分配而采用的一种堆表,称为快表是因为:该单向链表中永远不会发生堆块合并,快表也有128条,与空表不同的是每条快表最多只有4个节点。03
linux为什么选择2倍方式扩容
linux下主要使用glibc中ptmalloc来进行空间申请的。如果malloc的空间小于128KB,其内部通过brk()来扩张,如果大于128KB且arena中没有足够的空间时,通过mmap将内存空间映射到进程地址空间。每次申请出来的小块内存,对应一个chunk,根据作用不同,chunk(内存块的意思)总共分为4类:Allocated chunk:即分配给用户且未释放的内存块
Free chunk:即用户已经释放的内存块
Top chunk:一个arena顶部的chunk叫做Top chunk,它不属于bin,当所有bin中都没有可用的chunk时,切割Top chunk来满足用户内存申请。
Last Remainder chunk:当需要分配一个较小的k字节的chunk但是small bins中找不到满足要求的,且Last Remainder chunk的大小N能满足要求,那么Last Remainder chunk将被切割。
10个fast bins,存储在fastbinsY中
10个LIFO的单链表,相邻空闲chunk不会被合并,主要用于提高小块内存分配和释放效率
1个unsorted bin,存储在bin[1]中
大小超过fast bins阈值的chunk被释放时会添加到这里,目的复用最近释放的chunk,从而消除寻找合适bin的时间开销,从而提高内存分配和释放的效率。
62个small bins,存储在bin[2]到bin[63]
小于0x200(0x400-64位系统)Byte的chunk叫做small chunk,chunk的大小从16Byte开始,每次递增8字节,small bin是就存储这些小chunk,small bin是62个双向循环链表,与fast bin是不同的是:small bins是FILO的,且相邻空闲的chunk会被合并。
chunk大小:0x10~0x1f0Byte范围(0x20~0x3f0---64位系统),相邻bin存放的chunk大小相差0x8(0x10)Byte
63个large bins,存储在bin[64]到bin[126]
大于等于0x200(0x400---64为系统)Byte的chunk叫做large chunk,存放在large bins中。large bins是63个双向循环链表,插入和删除可以发生在任意位置,相邻空闲chunk也会被合并。chunk大小比较复杂:
1、前32个bins:从0x200Byte开始每次加0x40Byte2、接下来的16个bins:每次加0x200Byte3、接下来8个bins:每次加0x1000Byte4、接下来4个bins:每次加0x8000Byte5、接下来2个bins:每次加0x4000Byte6、最后一个bin:只有一个chunk,大小和large bins剩余的大小相同注意:同一个bin中的chunks不是相同大小的,按大小降序排列,在分配取出时遵循best-fit原则,即取出满足大小最小的chunk。
以上为linux中管理释放内存块的方式,即在对chunk进行管理时,也会进行合并,既然会合并按道理讲应该按照1.5倍方式扩容才比较合适,多次申请释放之后可以利用之前被释放的空间,但为什么SGI-STL的vector选择2倍方式扩容?我猜想:Linux中引入伙伴系统为内核提供了一种用于分配一些连续的页而建立的一种高效的分配策略,对固定分区和动态分区方式的折中,固定分区存在内部碎片,动态分区存在外部碎片,而且动态分区回收时的合并以及分配是的切分是比较耗时的。伙伴系统是将整个内存区域构建成基本大小basicSize的1倍、2倍、4倍、8倍、16倍等,即要求内存空间分区链均对应2的整次幂倍大小的空间,整齐划一,有规律的而不是乱糟糟的。在分配和释放空间时,可以通过log2(request/basicSize)向上取整的哈希算法快速找到对应内存块。04
总结
本节课我们主要讨论了vs为什么选择以1.5倍方式扩容,linux选择以2倍方式扩容,通过本节和上节课的讨论,相信大家对vector扩容机制相关问题已经非常熟悉,希望大家在面试时能得心应手,拿到心仪的offer,谢谢。作者:时亮益
审稿:王海斌编辑:啊琛琛点亮"在看",点亮"offer"

原创不易,点个赞吧~