博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
探究操作系统的内存分配(malloc)对齐策略
阅读量:5891 次
发布时间:2019-06-19

本文共 6967 字,大约阅读时间需要 23 分钟。

问题:

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

(对于上面计算的由来可以参见这篇文章的第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

 

 

 参考资料:

转载于:https://www.cnblogs.com/Creator/archive/2012/04/05/2433386.html

你可能感兴趣的文章
web.xml中的url-pattern映射规则
查看>>
图像的下采样Subsampling 与 上采样 Upsampling
查看>>
SQL 数据类型
查看>>
支付宝接口调用,支付操作
查看>>
如何在ashx页面获取Session值 (仅供个人参考)
查看>>
cookie与session
查看>>
Linux经常用到的命令以及快捷键
查看>>
计算题:挣值、预测、沟通、盈亏平衡点、
查看>>
js 实现 aop
查看>>
Dalvik VM和JVM的比较以及Android新的虚拟机ART
查看>>
【CSU 1803】2016
查看>>
SQLServer 批量备份与还原
查看>>
51Nod 1010 只包含因子2 3 5的数 Label:None
查看>>
Java中String和byte[]间的转换浅析
查看>>
什么是异步
查看>>
WordPress 主题切换
查看>>
cookie和session
查看>>
【java】path和classpath
查看>>
UVa 10057 - A mid-summer night's dream
查看>>
解决3 字节的 UTF-8 序列的字节 3 无效
查看>>