首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >map容器clear操作不会释放内存?

map容器clear操作不会释放内存?

原创
作者头像
洛杉矶
修改2020-10-22 09:14:46
15.3K0
修改2020-10-22 09:14:46
举报
文章被收录于专栏:技术最杂谈技术最杂谈

一,map容器clear操作不会释放内存?

当第一次听到这个说法的时候确实有点惊讶。一直记得map容器底层红黑树会自动析构节点,并释放内存。在同事进行了代码验证,并百度了答案后,我也变得不确定起来了。

只有再次看了一遍《STL源码剖析》。希望最终能够回答这个问题。

最后发现这个问题即对又不对。为什么呢?看后面的分析。

二,map的clear操作

2.1 clear源码

Void clear(){t.clear();}; //P241页《STL源码剖析》

由于关联容器底层是红黑树实现,所以map的clear也是调用的红黑树的clear函数。

那么我们接下来再看看红黑树的clear操作。

在开发机中可以找到文件stl_tree.h,erase操作的源码如下:

该操作中核心调用的destroy_node分成两步:

1,先析构元素,2,然后释放内存。(destroy_node操作与《STL源码剖析》书中的写法有一点出入,不过本质是一样的)

其中步骤2释放内存操作代码如下:

这里的deallocate操作就是STL中封装的全局内存分配操作函数。

读到这里就比较清楚了,map容器的erase以及clear操作底层都是调用的全局函数deallocate进行内存释放操作。

2.2 deallocate源码分析

Std::alloc为内存配置器,具体又分为一级和二级配置器。负责为容器提供内存空间操作,提供了常用的两个操作:allocate分配内存,deallocate释放内存。

一级配置器与二级配置器的区别:

如果申请的内存块大于128字节,则STL使用一级配置器。Allocate底层调用malloc操作,deallocate调用free操作。

如果申请的内存块小于128字节,则使用二级配置器。该配置器具体实现类似于一个内存池。减少频繁系统,提高内存分配效率。缺点:为了维护小块内存,需要额外的空间来进行管理。

2.3 小结

到这里就可以有一个小结论。

1,当map中的元素占用内存大小总和小于128字节时,则erase或者clear操作确实不会释放内存(包括虚拟和物理内存)。

2,当元素对象大于或等于128字节,则直接系统调用malloc或者free进行内存的分配(malloc只是分配虚拟内存)

3,只有当对分配的内存区域初始化时,操作系统才会分配物理内存,产生minflt小缺页。

三,STL中常用容器内存操作总结

3.1 vector容器

3.1.1 空容器

容量大小为0。

vector<int> v;

cout<<"size: "<<v.size()<<", capacity: "<<v.capacity()<<endl;//0 0

3.1.2 写入操作(push_back等)

容量变化:

如果是对容量为0的容器进行一次push_back操作,则容器大小变为1。

如果该容器容量已满,则会对容器容量扩容一倍,并把旧容器的元素拷贝至新内存中。

元素构造:

如果容器没有容量,则分两步完成操作:先allocate分配内存,然后construct构造元素。

如果有足够容量,则只调用construct构造元素即可。

3.1.3删除操作(pop_back,erase,clear等)

只调用析构函数destroy,并不会进行内存的释放。即容器的capacity并不会变化。

3.1.4 析构函数

可以看到,即调用了析构函数,也调用了内存释放函数。

所以常常vector或者string在进行一系列操作后,容量变得非常大,那么可以通过下面的技巧进行容量的缩减。

vector<int> v;

v.push_back(1);

v.push_back(2);

……

vector<int>().swap(v); // 当clear()用。和临时对象交换,交换后临时对象消失。真正将v释放掉。

// or: v.swap(vector<int>());

3.2 list操作

3.2.1 empty操作

Empty操作性能好于size()==0。《Effective STL》

3.2.2 写入操作(insert)

两步完成:1,分配内存。2,构造node,并进行赋值。

3.2.3 删除操作(pop_back,erase等)

分别调用析构函数,内存释放函数完成操作。

3.3 deque双端队列

分段连续,随机迭代器。没有容量的概念

3.3.1 初始状态

至少存在一个缓冲区。

3.3.2 写入操作

如果分段缓冲区还有空间,则直接调用constuct操作。否则分配内存,分段缓冲区,然后构造该元素。

3.3.3 删除操作

如果删除的一段缓冲区还有数据,则只析构对象,并不释放内存。

如果删除后,该段缓冲区没有数据,则析构元素,并释放内存。

Clear或者erase操作后,会保留一个缓存区,只对其析构元素。其他缓冲区即析构函数,也释放内存。

3.4 关联容器之map

关联容器都是红黑树(hash_xx除外)。具有较高的查找和插入效率,元素有序。

3.4.1 写入操作

Insert操作会先分配内存,初始化树node节点,node节点元素类型为pair。

3.4.2 下标操作

Map容器的下标操作既可以作为左值,也可以作为右值。因为其返回的是引用。

也是一个效率非常低的函数,尽量少用。

另外一个副作用是:当key值在map中不存在时,会自动在map中插入一个key,value为默认值的元素。造成map结构增大。

如下代码:

map<int, int> map_int; cout<<"size "<<map_int.size()<<endl;//0 for(int i = 0; i != 10; i++ ) { int j = map_int[i]; }

cout<<"size "<<map_int.size()<<endl;//10

3.4.3 删除操作

Erase等删除操作,即会析构元素,也会释放元素占用的内存。

四,操作系统内存分配

通过上面的分析,基本上弄清楚了STL容器对内存的分配和释放原理。

最终是调用malloc或者free来进行内存分配的。

参考《十问 Linux 虚拟内存管理 (glibc)》http://km.oa.com/group/17613/articles/show/135448?kmref=search

4.1 glibc内存分配原理

1,当分配块小于128K时,通过brk系统调用控制堆顶往高方向变化。

2,当分配块大于128K时,则通过mmap2和munmap来进行内存的分配和释放的。

4.2

频繁minflt

因此,当频繁分配大内存(大于128K)时,则会调用mmap。Free操作时,会立即调用munmap把该物理和虚拟内存归还给操作系统。会有较高的系统耗时以及minflt。

常常通过命令pidstat –p pid –r 1即可查看。

通过strace –p pid –T –ttt –c 命令,可以看到大量的mmap操作。

这种情况下,常常可以禁用mmap或者提高128K这个门槛来减少mmap系统调用。

Spp框架在实现中有如下代码:

mallopt(M_MMAP_THRESHOLD, 1024*1024); // 1MB,防止频繁mmap,大于1M,则使用mmap分配内存

mallopt(M_TRIM_THRESHOLD, 8*1024*1024); // 8MB,防止频繁brk,大于8M,才使用brk归还内存

这里需要注意的是,通过M_MMAP_THRESHOLD设置后,大于1M肯定是mmap分配内存,但是当分配内存小于1M时,还是可能通过mmap分配内存。具体见描述info mallopt

另外一种解决进程有较大minflt以及频繁系统调用mmap分配内存策略。完全禁止mmap操作以及禁止内存紧缩。

mallopt(M_MMAP_MAX, 0); // 禁止malloc调用mmap分配内存

mallopt(M_TRIM_THRESHOLD, -1); // 禁止内存紧缩

带来的缺点:

会存在进程持有内存不释放的现象,进程占用内存较高,造成假性内存泄漏。

4.3 内存紧缩

当通过brk分配内存时,堆连续空闲内存大于128K,则内存紧缩,将物理内存归还给操作系统。

对于持续分配较多小内存的程序时,这也会降低内存的使用率。因此可以通过调节紧缩大小或者禁用紧缩策略来解决。

方法1,

mallopt(M_TRIM_THRESHOLD, 8*1024*1024); // 8MB,防止频繁brk,大于8M,才使用brk归还内存

方法2,

mallopt(M_TRIM_THRESHOLD, -1); // 禁止内存紧缩

五,结论

1,map的删除操作,其本身肯定会释放元素占用的内存。

2,具体是否释放进程虚拟地址空间和物理内存,与内存gblic分配策略方式有关,而不是map本身的特性。

3,malloc操作只是分配虚拟地址空间,只有当对该内存区域有写操作时,才会产生minflt,分配物理内存。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一,map容器clear操作不会释放内存?
  • 二,map的clear操作
    • 2.1 clear源码
      • 2.2 deallocate源码分析
        • 2.3 小结
        • 三,STL中常用容器内存操作总结
          • 3.1 vector容器
            • 3.1.1 空容器
            • 3.1.2 写入操作(push_back等)
            • 3.1.3删除操作(pop_back,erase,clear等)
            • 3.1.4 析构函数
          • 3.2 list操作
            • 3.2.1 empty操作
            • 3.2.2 写入操作(insert)
            • 3.2.3 删除操作(pop_back,erase等)
          • 3.3 deque双端队列
            • 3.3.1 初始状态
            • 3.3.2 写入操作
            • 3.3.3 删除操作
          • 3.4 关联容器之map
            • 3.4.1 写入操作
            • 3.4.2 下标操作
            • 3.4.3 删除操作
        • 四,操作系统内存分配
          • 4.1 glibc内存分配原理
            • 4.2
              • 4.3 内存紧缩
              • 五,结论
              相关产品与服务
              容器服务
              腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档