问题:

  我们在写程序的时候经常发现程序使用的内存往往比我们申请的多,为了优化程序的内存占用,搅尽脑汁想要优化内存占用,可是发现自己的代码也无从优化了,怎么办?现在我们把我们的焦点放到malloc上,毕竟我们向系统申请的内存都是通过它完成了,不了解他,也就不能彻底的优化内存占用。

来个小例子

//g++ -o malloc_addr_vec  mallc_addr_vec.cpp 编译
 2 #include<iostream>
 3 using namespace std;
 4 int main(int argc, char *argv[])
 5 {
 6     int malloc_size = atoi(argv[1]);
 7     char * malloc_char;
 8     for (size_t i = 0; i < 1024*1024; ++i) {
 9         malloc_char = new char[malloc_size];
10     }
11     while (1) {}//此时查看内存占用
12     return 0;
13 }

本文的测试环境为Linux 64Bit ,使用G++编译为可执行文件后,使用不同的启动参数启动,使用top命令查看程序占用的内存,这里我们主要是看RES指标

RES  --  Resident size (kb)

The non-swapped physical memory a task has used.

测试案例:

1.每次new 1 Byte   Do 1024*1024次

./malloc_addr_vec 1

启动程序后的内存占用

内存消耗 32MB

2.每次new 24 Byte  Do 1024*1024次

./malloc_addr_vec 24

启动程序后的内存占用

内存消耗32MB

3.每次new 25 Byte   Do 1024*1024次

./malloc_addr_vec 25

启动程序后的内存占用

内存消耗48MB

  为什么我们每次new 1Byte 和每次 new 24Byte系统消耗的内存一样呢?,为什么每次new 25Byte和 每次new 24Byte占用的内存完全不同呢?

  不知道大家在写程序的时候有没有关注过这个问题。我一次遇到时,吐槽一句:What the fuck malloc.

原因分析:

  在大多数情况下,编译器和C库透明地帮你处理对齐问题。POSIX 标明了通过malloc( ), calloc( ), 和 realloc( ) 返回的地址对于任何的C类型来说都是对齐的。

  对齐参数(MALLOC_ALIGNMENT) 大小的设定并需满足两个特性

1.必须是2的幂

2.必须是(void *)的整数倍

  至于为什么会要求是(void *)的整数倍,这个目前我还不太清楚,等你来发现...

  根据这个原理,在32位和64位的对齐单位分别为8字节和16字节

  但是这并解释不了上面的测试结果,这是因为系统malloc分配的最小单位(MINSIZE)并不是对齐单位

为了进一步了解细节,从GNU网站中把glibc源码下载下来,查看其 malloc.c文件

  其中request2size这个宏就是glibc的内存对齐操作,MINSIZE就是使用malloc时占用内存的最小单位。根据宏定义可推算在32位系统中MINSIZE为16字节,在64位系统中MINSIZE一般为32字节。从request2size还可以知道,如果是64位系统,申请内存为1~24字节时,系统内存消耗32字节,当申请内存为25字节时,系统内存消耗48字节。 如果是32位系统,申请内存为1~12字节时,系统内存消耗16字节,当申请内存为13字节时,系统内存消耗24字节。

一般他们的差距是一个指针大小,计算公式是

max(MINSIZE,in_use_size)

其中in_use_size=(要求大小+2*指针大小-指针大小)align to MALLOC_ALIGNMENT

(对于上面计算的由来可以参见glibc 内存池管理 ptmalloc这篇文章的第4节chuck部分以及搜一下malloc的内部实现源码 )

  为了证明这个理论的正确性,我们需要计算一次malloc到底花掉了多少内存,我们用如下代码分别在32bit Linux和 64bit Linux上做测试

2 #include<stdio.h>
 3 #include<stdlib.h>
 4 int main()
 5 {
 6         char * p1;
 7         char * p2;
 8         int i=1;
 9         printf("%d\n",sizeof(char *));
10         for(;i<100;i++)
11         {
12                 p1=NULL;
13                 p2=NULL;
14                 p1=(char *)malloc(i*sizeof(char));
15                 p2=(char *)malloc(1*sizeof(char));
16                 printf("i=%d     %d\n",i,(p2-p1));
17         }
18 
19         getchar();
20 }

其测试结果如下:

32bit

64bit

  

了解了malloc的内存对其原理后,对于程序的内存占用的优化又有了有的放矢。我们可以根据内存对齐的原则来请求内存,来制作我们的高效内存池,从而避免隐形的资源浪费.

例如,目前STL的内存池是以8Byte为对齐单位,内存池free_list大小为

free_list[0] --------> 8 byte

free_list[1] --------> 16 byte

free_list[2] --------> 24 byte

free_list[3] --------> 32 byte
  ... ...

free_list[15] -------> 128 byte

STL内存池在发现某个规则的内存用完了时,会进行refill,在进行chunk_alloc

例如8Byte大小的空间没有了,调用refill,refill会将其空间准备20个,也就是20*8,当然refill做不了内存分配,他把20个8Byte的需求提交给chunk_alloc

chunk_alloc 能真正分配内存,但是它分配的时候会将内存空间*2,所以最终malloc的内存为8*20*2=320 ,32bit系统给malloc的内存为328,64bit系统给malloc的内存为336

在32位和64位操作系统分别浪费掉8Byte和16Byte,其实我们可以在chunk_alloc内部简单的计算一下系统的内存对齐,达到 chunk_alloc 级零浪费...
至于 allocate级别的浪费,我想是避免不了了,譬如,我需要一个6Byte的空间,STL内存池给我的确实8Byte

探究操作系统的内存分配(malloc)对齐策略的更多相关文章

  1. Java虚拟机垃圾回收:内存分配与回收策略 方法区垃圾回收 以及 JVM垃圾回收的调优方法

    在<Java对象在Java虚拟机中的创建过程>了解到对象创建的内存分配,在<Java内存区域 JVM运行时数据区>中了解到各数据区有些什么特点.以及相关参数的调整,在<J ...

  2. 【007】【JVM——内存分配和恢复策略】

     内存分配与收回策略 JVM的自己主动内存管理要自己主动化地解决两个问题:对象分配内存以及回收分配给对象的内存.回收内存前几篇已经讲了.如今说内存分配.对象的内存分配一般分配在堆内存中,也可能经过 ...

  3. Java虚拟机内存分配与回收策略

    内存分配与回收策略 Minor GC 和 Full GC Minor GC:发生在新生代上,因为新生代对象存活时间很短,因此 Minor GC 会频繁执行, 执行的速度一般也会比较快. Full GC ...

  4. JVM垃圾回收器、内存分配与回收策略

    新生代垃圾收集器 1. Serial收集器 serial收集器即串行收集器,是一个单线程收集器. 串行收集器在进行垃圾回收时只使用一个CPU或一条收集线程去完成垃圾回收工作,并且会暂停其他的工作线程( ...

  5. JVM学习十 -(复习)内存分配与回收策略

    内存分配与回收策略 对象的内存分配,就是在堆上分配(也可能经过 JIT 编译后被拆散为标量类型并间接在栈上分配),对象主要分配在新生代的 Eden 区上,少数情况下可能直接分配在老年代,分配规则不固定 ...

  6. [转]内存分配malloc, new , heapalloc

    malloc,new,VirtualAlloc,HeapAlloc性能(速度)比较 http://www.cppblog.com/woaidongmao/archive/2011/08/12/1531 ...

  7. JVM的内存分配和回收策略

    对象的Class加载 虚拟机遇到一条new指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载.解析和初始化过.如果没有,那必须先执行相应 ...

  8. 转: Linux C 动态内存分配 malloc及相关内容 .

    一.malloc()和free()的基本概念以及基本用法: 1.函数原型及说明: void *malloc(long NumBytes):该函数分配了NumBytes个字节,并返回了指向这块内存的指针 ...

  9. 浅谈java内存分配和回收策略

    一.导论 java技术体系中所提到的内存自动化管理归根结底就是内存的分配与回收两个问题,之前已经和大家谈过java回收的相关知识,今天来和大家聊聊java对象的在内存中的分配.通俗的讲,对象的内存分配 ...

随机推荐

  1. 华为上机试题(java)

    一.题目描述:通过键盘输入一串小写字母(a~z)组成的字符串.请编写一个字符串过滤程序,若字符串中出现多个相同的字符,将非首次出现的字符过滤掉.比如字符串“abacacde”过滤结果为“abcde”. ...

  2. [Angular] Scrolling the Message List To the Bottom Automatically Using OnChanges

    Let's say the message list can Input (messages) from parent component, and what we want to do is whe ...

  3. 微信小程序从零开始开发步骤(四)

    上一章节,实现了小程序的底部导航的功能,这一节开始实现一些简单的功能.本章节介绍的是小程序的自定义分享的功能. 可以分享小程序的任何一个页面给好友或群聊.注意是分享给好友或群聊,并没有分享到朋友圈.一 ...

  4. js进阶 9-12 如何将数组的信息添加到下拉列表

    js进阶 9-12 如何将数组的信息添加到下拉列表 一.总结 一句话总结:创建出select的option,然后selectElement的add方法加进 selectElement 即可 1.创建出 ...

  5. C#写COM组件,JS调用控件

    1.c#2005中新建项目,类型为类库,项目名为AddCom确定. 配置:右键点击解决方案资源管理器中的AddCom,选择“属性”,选择“生成”,选择“为COM Interop注册(_P)” 2.打开 ...

  6. Springboot系列:@SpringBootApplication注解

    在使用 Springboot 框架进行开发的时候,通常我们会在 main 函数上添加 @SpringBootApplication 注解,今天为大家解析一下 @SpringBootApplicatio ...

  7. CUDA—使用GPU暴力破解密码

    GPU支持大规模的并行加速运算,胜在量上,CPU处理大量的并行运算显得力不从心,它是胜在逻辑上.利用显卡加速的应用越来越多,但如果说GPU即将或最终将替代CPU还有点言过其实,二者最终将优势互补,各尽 ...

  8. twemproxy

    twemproxy架构分析——剖析twemproxy代码前编   twemproxy背景 在业务量剧增的今天,单台高速缓存服务器已经无法满足业务的需求, 而相较于大容量SSD数据存储方案,缓存具备速度 ...

  9. Delphi 禁用x64位系统文件重定向功能

    在X64系统里面,一些特殊的目录和特殊的注册表键被分为2个独立的部分.对于文件系统来说,      %systemroot%\system32 目录被保留给64位文件使用,而32位文件会被重定向到%s ...

  10. codeforces Round #259(div2) D解决报告

    D. Little Pony and Harmony Chest time limit per test 4 seconds memory limit per test 256 megabytes i ...