1. 概述
linux软件系统分为内核和应用程序,我觉得主要使用内存是在应用程序。应用程序是以进程和动态库为单位,内存优化应该是以进程和动态库来进行优化。
- 代码段: (text)存放代码数据
- 数据段:(data)存放程序中已初始化的全局变量的一块内存区域,static声明的变量也存在这里
- bss段:存放程序中未初始化的全局变量的一块内存区域
- 堆区:(heap)存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。
- 栈区:(stack)栈又称堆栈,是用户存放程序临时创建的局部变量
2. 内存优化
linux进程由五段组成,BSS段、数据段、代码段、堆、栈组成。代码段是全系统共享,所以进程内存优化主要是在BSS段、数据段、堆、栈内进行。如下记录常用的优化方案。
2.1 堆段:
在进程中、我们可以调用 mallopt 函数,来调整libc的内存管理先行为。
- M_TRIM_THRESHOLD 堆顶内存回收阀值,默认值为128K。堆顶空闲内存达到阀值时,调用系统brk,将内存归还给系统。
- M_TOP_PAD 堆顶保留空间内存的数量,默认值为0
- M_MMAP_THRESHOLD libc中大块内存阀值。大于该阀值,系统调用mmap申请内存。默认值为128K。
2.2 栈段:
linux系统中进程栈段的内存使用增加后,函数返回不会被释放,但是下次进入函数是可以复用的。所以栈段的内存只会增加不 会减少。对我们来讲,尽量少用递归函数及在函数内分配大块内存。
优化方法:
- 尽量 避免在栈空间申请大量内存。
- 尽量避免使用递归函数。
环境变量及参数及优化方法:
- 新增环境变量是存放在堆区。修改环境变量存在新申请的堆空间,不会释放原内存。
- 尽量在进程启动行设置好环境变量,这样环境变量可以紧密排列在栈空间。
linux bin文件是ELF的文件类型。
读取ELF文件的内容命令:
readelf -a [binFileName]
strip bin文件 :删除bin文件内的GDB调试信息。删除bin文件的调试信息后,bin文件基本上可以缩小一倍以上。
2.3 数据段:
数据段的优化主要是在.bss和.data。
- .bss : 保存未初始化或者初始化为0的全局变量和静态变量, bss基本不占用ELF文件空间
- .data : 保存初始化不为0的全局变量和静态变量
对进程自身数据段优化,影响是有限的。对于动态库数据段做优化,效果是比较明显。
2.3.1 数据段优化:
2.3.1.1 尽量减少全局变量和静态变量
采用nm来列出所有在.data和.bss节的变量,方便我们检查。
查看动态库.data和.bss节数据:
nm --format=sysv libfile | grep -w .data
nm --format=sysv libfile | grep -w .bss
输出对应的列信息:
name value class type size lineSection
2.3.1.2 对于非内置类型的全局变量,尽可能采用类指针来代替。
在main函数之前,运行所有非内置类型的全局变量,一方面会降低启动速度,另一方面会浪费物理内存。
对于静态的非内置类型的静态对象,在第一引用时创建对象。
2.3.1.3 将只读的全局变量,加上const使其转移到代码段。利用代码段共享来节省内存。
对类对象的全局变量,加上const也无法转移到.rodata段。原因是类要运行其构造函数,有可能对成员变量赋值。
2.3.1.4 字符串数组优化
将数组改成用函数switch case返回值。
2.4 代码段:
代码段是整个系统共享。在内存不足时还能够回收。因此,代码段优化的意义不大。
主要有以下几点优化:
- 1、编译主程序时不要使用-export-dynamic。
- 2、删除冗余代码
gcc有编译选择来检测冗余代码,并显示warning信息
-Wunused
–Wunreachable-code
2.5 动态库:
动态库技术是为了减少程序的大小,节省空间,提高效率,具有很高的奶油灵活性。由于动态库不是程序的一部分,是需要时载入,其执行代码可能在多个程序间共享。运行期间需要查找动态库,对程序的性能会有所损失。
进程对应动态库加载方法有如下两种:
- 静态加载
- 动态加载
动态加载是使用函数dlopen,dlclose。动态调用麻烦,还要关注动态库的生存周期。共享库只有数据段和代码段。
不要在进程中通过extern的方式引用共享库中的全局变量。一旦引用都会占用物理内存,同时还会增加系统的启动时间。
共享库全局变量:
- 1、全局变量声明在进程中,在共享库中使用,该变量位于进程数据段。
- 2、全局变量声明在共享库中,在进程中使用,变量会被复制到进程的数据段,同时修改使用该变量的共享库的指向。
- 3、全局变量在共享库中声明,在共享库中使用,则该变量位于声明它共享库的数据段中。
代码段共享:
gcc编译时必须加-fPIC参数进行编译。不加时,共享库无法真正共享。
共享库内导出函数对代码段影响:
共享库所定义的函数默认都是导出函数,每增加一个函数就是增加 24+函数名长度(byte)。
GCC通过--version-script选项来指定我们的导出函数。
动态库优化:
动态库数据段所占内存随着依赖他的进程数量成线性增长,代码段则是系统共享,所以将优化重点放在动态库的数据段。