程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> 解析操作系統的內存分配(malloc)對齊策略

解析操作系統的內存分配(malloc)對齊策略

編輯:關於C++

問題:

我們在寫程序的時候經常發現程序使用的內存往往比我們申請的多,為了優化程序的內存占用,攪盡腦汁想要優化內存占用,可是發現自己的代碼也無從優化了,怎麼辦?現在我們把我們的焦點放到malloc上,畢竟我們向系統申請的內存都是通過它完成了,不了解他,也就不能徹底的優化內存占用。

來個小例子

//g++ -o malloc_addr_vec  mallc_addr_vec.cpp 編譯  
#include<iostream>  
using namespace std;  
int main(int argc, char *argv[])  
{  
    int malloc_size = atoi(argv[1]);  
    char * malloc_char;  
    for (size_t i = 0; i < 1024*1024; ++i) {  
        malloc_char = new char[malloc_size];  
    }  
    while (1) {}//此時查看內存占用  
    return 0;  
}

本文的測試環境為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文件

#ifndef INTERNAL_SIZE_T    
#define INTERNAL_SIZE_T size_t    
#endif    
#define SIZE_SZ                (sizeof(INTERNAL_SIZE_T))    
#ifndef MALLOC_ALIGNMENT    
#define MALLOC_ALIGNMENT       (2 * SIZE_SZ)    
#endif    
      
      
struct malloc_chunk {    
  INTERNAL_SIZE_T      prev_size;  /* Size of previous chunk (if free).  */
  INTERNAL_SIZE_T      size;       /* Size in bytes, including overhead. */
  struct malloc_chunk* fd;         /* double links -- used only if free. */
  struct malloc_chunk* bk;    
};    
      
    An allocated chunk looks like this:    
    chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+    
            |             Size of previous chunk, if allocated            | |    
            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+    
            |             Size of chunk, in bytes                       |M|P|    
      mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+    
            |             User data starts here...                          .    
            .                                                               .    
            .             (malloc_usable_size() bytes)                      .    
            .                                                               |    
nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+    
            |             Size of chunk                                     |    
            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+    
                  
                  
#define MALLOC_ALIGN_MASK      (MALLOC_ALIGNMENT - 1)    
#define MIN_CHUNK_SIZE        (sizeof(struct malloc_chunk))    
#define MINSIZE  /    
  (unsigned long)(((MIN_CHUNK_SIZE+MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK))    
/* pad request bytes into a usable size -- internal version */
#define request2size(req)                                         /    
  (((req) + SIZE_SZ + MALLOC_ALIGN_MASK < MINSIZE)  ?             /    
   MINSIZE :                                                      /    
   ((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上做測試

#include<stdio.h>  
#include<stdlib.h>  
int main()  
{  
        char * p1;  
        char * p2;  
        int i=1;  
        printf("%d\n",sizeof(char *));  
        for(;i<100;i++)  
        {  
                p1=NULL;  
                p2=NULL;  
                p1=(char *)malloc(i*sizeof(char));  
                p2=(char *)malloc(1*sizeof(char));  
                printf("i=%d     %d\n",i,(p2-p1));  
        }  
      
        getchar();  
}

其測試結果如下:

32bit

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

64bit

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

作者:cnblogs 大熊(先生)

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved