• 探究操作系统的内存分配(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

     参考资料:http://blog.csdn.net/elpmis/article/details/4500917


    博客地址:Zealot Yin

    欢迎转载,转载请注明出处[http://creator.cnblogs.com/]

  • 相关阅读:
    d3.js--04(enter和exit)
    d3.js--03(增删改查)
    d3.js--02(data和datum原理)
    css文件和js文件后面带一个问号----2015-1103
    json_decode()和json_encode()区别----2015-0929
    随记--更新于20150925
    个人火狐插件汇总--20150924
    『jQuery』.html(),.text()和.val()的概述及使用--2015-08-11
    矩阵求导
    傅里叶变换
  • 原文地址:https://www.cnblogs.com/Creator/p/2433386.html
Copyright © 2020-2023  润新知