系统软件性能低下的问题,主要与三种因数有关:CPU、内存、IO。
如何优化系统的性能?
第一步,找出系统性能的瓶颈。
(1)程序的运算量很大,CPU过于繁忙,CPU是瓶颈。
可以通过top命令观察CPU的利用率情况。
(2)过多或过于频繁的读写文件、内存等I/O操作,CPU更多的是处于等待,(存储设备、网络设备、异步IO)等I/O操作是性能瓶颈。
(3)内存不够。物理内存不够时会使用交换内存, 使用交换内存会带来磁盘IO和CPU的开销增加.交换空间是专门用于临时存储内存的一块磁盘空间,通常在页面调度和交换进程数据时使用.调页算法是将内存中最近不常使用的页面换到磁盘上,把常使用的页面(活动页面)保留在内存中供进程使用。当程序运行需要的内存大于物理内存时,linux系统采用了调页机制,即系统copy一些内存中的页面到磁盘上,腾出来空间供进程使用。
大多数系统可以忍受偶尔的调页,但是频繁的调页会使系统性能急剧下降。
(4) 带宽。如网络带宽、系统总线带宽等
(5)文件系统性能(大文件优化、小文件优化、写优化、读优化、网络文件系统)。
(6) 多线程性能
一些经验:
1. 小文件读写的性能瓶颈是磁盘的寻址(随机读写性能更差),评估的标准是tps
2. 大文件读写的性能瓶颈是带宽,评估的标准是持续的读写速度
3. Linux可以利用空闲内存作文件系统访问的cache,因此系统内存越大存储系统的性能也越好
第二步,着手进行优化
1.堆内存和数据段内存的优化:
malloc一般会实现一个内存堆来管理这些内存,malloc分配的内存都会以若干chunk的方式放到内存堆中。每次用户调用malloc动态分配内存的时候,malloc会先到内存堆里进行查找,如果内存堆里没有合适的空闲chunk,再利用brk/malloc系统调用分配一大块内存,然后把新分配的大块内存放到内存堆中,并生成一块合适的chunk块返回给用户。
当用户用free释放chunk的时候,可能并不立即使用系统调用释放内存,而是将释放的chunk作为空闲chunk加入内存堆中,和其他的空闲chunk合并,便于下次分配的时候再次使用。
一般说来,释放的chunk如果标记为mmap申请的,则使用munmap释放。如果是brk申请的,进一步判断堆顶之下的空闲chunk是否大于128KB,如果是,则使用brk()释放。如果小于128KB,仍由内存堆维护。这样对brk()的使用就会有个问题,当brk()释放的内存块在堆顶之下,且内存块到堆顶之间还有未释放的内存。那么这块内存的释放将不会成功,从而形成内存空洞。
glibc管理的内存唯一释放的条件是堆顶存在128k(M_TRIM_THRESHOLD)或以上的空闲区时才会释放.这样就会形成内存空洞。
内存空洞:当存在内存空洞时,它会从空洞中分配,并不会导致内存使用的增加;
内存泄露:当libc收到内存分配请求时,只能占用更多内存。
针对大块内存的申请和释放不会导致内存空洞的形成。
堆内存的优化:
(1)glibc管理的内存绝大多数情况下不会释放。
因此编程时如果是小内存分配要尽快使用,尽快用完,尽快释放(变成空闲chunk),不要停留,否则一直摞着,线性地址后面的就形成了空洞。
(2)如果是想内存总在控制中,可以分配大内存,自行管理释放和分配。不用的时候可以释放地很干净
(3)不要分配很小的内存比如几个字节,因为一次malloc至少分配16个字节,如果每次分配都很小,就太亏了。
所以尽量减少小块内存的申请,避免内存浪费。
(4)降低M_MMAP_THRESHOLD,可以让更多的分配走mmap,避免brk的种种问题,特别是64位机器的情况下。
(5)降低M_TRIM_THRESOLD,让堆顶的空闲内存更容易释放。
以上(4)(5)都不可避免会增加系统调用的机会,使用中需要慎重。
数据段内存的优化:
(1)将只读的全局变量,加上const,从而使其转移到代码段;
(2)去除不必要的全局变量;
(3)减少全局变量的尺寸;
可以通过nm,查找到所有在数据段的符号,从而想办法修改它。
动态库的使用:
(1)静态加载:无法按需动态卸载动态库
(2)dlopen:可按需卸载动态库,从而节省了内存,但程序在启动时,需要查找定位代码,从而会降低性能。
代码优化的一些经验:
(1)运算处理优化:简化数学表达式、用移位运算代替乘除运算、乘法运算比除运算快、使用增量和减量运算符等;
(2)算法优化:根据发生频率对switch语句进行排列、将大的switch语句简化为嵌套switch语句等、用指针取代数组、使用宏函数取代函数。