探究操作系统的内存分配(malloc)对齐策略

我们在写程序的时候经常发现程序使用的内存往往比我们申请的多,为了优化程序的内存占用,搅尽脑汁想要优化内存占用,可是发现自己的代码也无从优化了,怎么办?现在我们把我们的焦点放到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文件

View Code
 1  #ifndef INTERNAL_SIZE_T  
 2  #define  INTERNAL_SIZE_T size_t  
 3  #endif  
 4  #define  SIZE_SZ                (sizeof(INTERNAL_SIZE_T))  
 5  #ifndef MALLOC_ALIGNMENT  
 6  #define  MALLOC_ALIGNMENT       (2 * SIZE_SZ)  
 7  #endif  
 8 
 9 
10  struct  malloc_chunk {  
11    INTERNAL_SIZE_T      prev_size;   /*  Size of previous chunk (if free).   */  
12    INTERNAL_SIZE_T      size;        /*  Size in bytes, including overhead.  */  
13    struct  malloc_chunk* fd;          /*  double links -- used only if free.  */  
14    struct  malloc_chunk* bk;  
15  };  
16 
17      An allocated chunk looks like  this :  
18      chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+  
19              |             Size of previous chunk,  if  allocated            | |  
20              +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+  
21              |             Size of chunk,  in  bytes                       |M|P|  
22        mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+  
23              |             User data starts here...                          .  
24              .                                                               .  
25              .             (malloc_usable_size() bytes)                      .  
26              .                                                               |  
27  nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+  
28              |             Size of chunk                                     |  
29              +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+  
30             
31             
32  #define  MALLOC_ALIGN_MASK      (MALLOC_ALIGNMENT - 1)  
33  #define  MIN_CHUNK_SIZE        (sizeof(struct malloc_chunk))  
34  #define  MINSIZE  /  
35    (unsigned  long )(((MIN_CHUNK_SIZE+MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK))  
36  /*  pad request bytes into a usable size -- internal version  */  
37  #define  request2size(req)                                         /  
38    (((req) + SIZE_SZ + MALLOC_ALIGN_MASK < MINSIZE)  ?             /  
39     MINSIZE :                                                      /  
40     ((req) + SIZE_SZ + MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK) 

   其中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

View Code
  1  ---------------------
  2  Linux  32bit
  3  --------------------- 
  4  4
  5  i= 1  16
  6  i= 2  16
  7  i= 3  16
  8  i= 4  16
  9  i= 5  16
 10  i= 6  16
 11  i= 7  16
 12  i= 8  16
 13  i= 9  16
 14  i= 10  16
 15  i= 11  16
 16  i= 12  16
 17  i= 13  24
 18  i= 14  24
 19  i= 15  24
 20  i= 16  24
 21  i= 17  24
 22  i= 18  24
 23  i= 19  24
 24  i= 20  24
 25  i= 21  32
 26  i= 22  32
 27  i= 23  32
 28  i= 24  32
 29  i= 25  32
 30  i= 26  32
 31  i= 27  32
 32  i= 28  32
 33  i= 29  40
 34  i= 30  40
 35  i= 31  40
 36  i= 32  40
 37  i= 33  40
 38  i= 34  40
 39  i= 35  40
 40  i= 36  40
 41  i= 37  48
 42  i= 38  48
 43  i= 39  48
 44  i= 40  48
 45  i= 41  48
 46  i= 42  48
 47  i= 43  48
 48  i= 44  48
 49  i= 45  56
 50  i= 46  56
 51  i= 47  56
 52  i= 48  56
 53  i= 49  56
 54  i= 50  56
 55  i= 51  56
 56  i= 52  56
 57  i= 53  64
 58  i= 54  64
 59  i= 55  64
 60  i= 56  64
 61  i= 57  64
 62  i= 58  64
 63  i= 59  64
 64  i= 60  64
 65  i= 61  72
 66  i= 62  72
 67  i= 63  72
 68  i= 64  72
 69  i= 65  72
 70  i= 66  72
 71  i= 67  72
 72  i= 68  72
 73  i= 69  80
 74  i= 70  80
 75  i= 71  80
 76  i= 72  80
 77  i= 73  80
 78  i= 74  80
 79  i= 75  80
 80  i= 76  80
 81  i= 77  88
 82  i= 78  88
 83  i= 79  88
 84  i= 80  88
 85  i= 81  88
 86  i= 82  88
 87  i= 83  88
 88  i= 84  88
 89  i= 85  96
 90  i= 86  96
 91  i= 87  96
 92  i= 88  96
 93  i= 89  96
 94  i= 90  96
 95  i= 91  96
 96  i= 92  96
 97  i= 93  104
 98  i= 94  104
 99  i= 95  104
100  i= 96  104
101  i= 97  104
102  i= 98  104
103  i= 99  104

 64bit

View Code
  1  -------------------
  2  Linux  64bit
  3  ------------------- 
  4  8
  5  i= 1  32
  6  i= 2  32
  7  i= 3  32
  8  i= 4  32
  9  i= 5  32
 10  i= 6  32
 11  i= 7  32
 12  i= 8  32
 13  i= 9  32
 14  i= 10  32
 15  i= 11  32
 16  i= 12  32
 17  i= 13  32
 18  i= 14  32
 19  i= 15  32
 20  i= 16  32
 21  i= 17  32
 22  i= 18  32
 23  i= 19  32
 24  i= 20  32
 25  i= 21  32
 26  i= 22  32
 27  i= 23  32
 28  i= 24  32
 29  i= 25  48
 30  i= 26  48
 31  i= 27  48
 32  i= 28  48
 33  i= 29  48
 34  i= 30  48
 35  i= 31  48
 36  i= 32  48
 37  i= 33  48
 38  i= 34  48
 39  i= 35  48
 40  i= 36  48
 41  i= 37  48
 42  i= 38  48
 43  i= 39  48
 44  i= 40  48
 45  i= 41  64
 46  i= 42  64
 47  i= 43  64
 48  i= 44  64
 49  i= 45  64
 50  i= 46  64
 51  i= 47  64
 52  i= 48  64
 53  i= 49  64
 54  i= 50  64
 55  i= 51  64
 56  i= 52  64
 57  i= 53  64
 58  i= 54  64
 59  i= 55  64
 60  i= 56  64
 61  i= 57  80
 62  i= 58  80
 63  i= 59  80
 64  i= 60  80
 65  i= 61  80
 66  i= 62  80
 67  i= 63  80
 68  i= 64  80
 69  i= 65  80
 70  i= 66  80
 71  i= 67  80
 72  i= 68  80
 73  i= 69  80
 74  i= 70  80
 75  i= 71  80
 76  i= 72  80
 77  i= 73  96
 78  i= 74  96
 79  i= 75  96
 80  i= 76  96
 81  i= 77  96
 82  i= 78  96
 83  i= 79  96
 84  i= 80  96
 85  i= 81  96
 86  i= 82  96
 87  i= 83  96
 88  i= 84  96
 89  i= 85  96
 90  i= 86  96
 91  i= 87  96
 92  i= 88  96
 93  i= 89  112
 94  i= 90  112
 95  i= 91  112
 96  i= 92  112
 97  i= 93  112
 98  i= 94  112
 99  i= 95  112
100  i= 96  112
101  i= 97  112
102  i= 98  112
103  i= 99  112
 

了解了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
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值