分配内存时如何减少内存碎片,分配内存碎片


  感觉面试的时候经常会被问到这个问题,然后我也学习了一下Memcached的slab机制,发现很多服务器都是使用这种机制来分配内存,所以决定学习一下。

  首先,先对内存分配中的伙伴系统有初步的了解:

  在编程和使用的服务器软件中,经常需要分配一组连续的页框,而频繁地申请和释放不同大小的连续页框,必然导致在已分配页框的内存块中分散了许多小块的空闲页框。这样,即使这些页框是空闲的,但要分配一个大块的连续页框就可能无法满足。   而Linux采用了伙伴系统来解决上述难题。把所有的空闲页框分组为11个块链表,每个块链表分别包含大小为1,2,4,8,16,32,64,128,256,512和1024个连续页框的页框块。最大可以申请1024个连续页框,对应4MB大小的连续内存。每个页框块的第一个页框的物理地址是该块大小的整数倍。例如,大小为16个页框的块,其起始地址是16×212的倍数。 假设要申请一个256个页框的块,先从256个页框的链表中查找空闲块,如果没有,就去512个页框的链表中找,找到了则将页框块分为2个256个页框的块,一个分配给应用,另外一个移到256个页框的链表中。如果512个页框的链表中仍没有空闲块,继续向1024个页框的链表查找,如果仍然没有,则返回错误。 页框块在释放时,内核会主动将两个互为伙伴的页框块合并为一个较大的页框块,成功后会试图寻找伙伴并合并为更大的内存块,直至块的大小超过上限或者没有伙伴为止。互为伙伴的两个内存块必须符合以下条件:   1、两个块具有相同的大小;   2、两个块的物理地址连续;   3、第一个快的物理地址是两个块大小的整数倍。     slab分配机制则是对伙伴算法的改进,slab(Slab Allocation)的设计理念是基于对象缓冲的,基本想法是避免重复大量的初始化和清理操作。slab主要可以用于频繁非配释放的内存对象。替代malloc/free
  改进的地方在于:

  它对内存区的处理并不需要进行初始化或回收。出于效率的考虑,Linux并不调用对象的构造或析构函数,而是把指向这两个函数的指针都置为空。Linux中引入Slab的主要目的是为了减少对伙伴算法的调用次数。

     实际上,内核经常反复使用某一内存区。例如,只要内核创建一个新的进程,就要为该进程相关的数据结构(task_struct、打开文件对象等)分配内存区。当进程结束时,收回这些内存区。因为进程的创建和撤销非常频繁,因此,Linux的早期版本把大量的时间花费在反复分配或回收这些内存区上。从Linux2.2开始,把那些频繁使用的页面保存在高速缓存中并重新使用。 可以根据对内存区的使用频率来对它分类。对于预期频繁使用的内存区,可以创建一组特定大小的专用缓冲区进行处理,以避免内碎片的产生。对于较少使用的内存区,可以创建一组通用缓冲区(如Linux2.0中所使用的2的幂次方)来处理,即使这种处理模式产生碎片,也对整个系统的性能影响不大。

    硬件高速缓存的使用,又为尽量减少对伙伴算法的调用提供了另一个理由,因为对伙伴算法的每次调用都会“弄脏”硬件高速缓存,因此,这就增加了对内存的平均访问次数。

Slab分配模式把对象分组放进缓冲区

     对于小对象, 就把Slab的描述结构slab_t放在该Slab中;对于大对象,则把Slab结构游离出来,集中存放。关于Slab中的着色区再给予具体描述:      每个Slab的首部都有一个小小的区域是不用的,称为“着色区(coloring area)”。着色区的大小使Slab中的每个对象的起始地址都按高速缓存中的”缓存行(cache line)”大小进行对齐(80386的一级高速缓存行大小为16字节,Pentium为32字节)。因为Slab是由1个页面或多个页面(最多为32)组成,因此,每个Slab都是从一个页面边界开始的,它自然按高速缓存的缓冲行对齐。但是,Slab中的对象大小不确定,设置着色区的目的就是将Slab中第一个对象的起始地址往后推到与缓冲行对齐的位置。因为一个缓冲区中有多个Slab,因此,应该把每个缓冲区中的各个Slab着色区的大小尽量安排成不同的大小,这样可以使得在不同的Slab中,处于同一相对位置的对象,让它们在高速缓存中的起始地址相互错开,这样就可以改善高速缓存的存取效率。      每个Slab上最后一个对象以后也有个小小的废料区是不用的,这是对着色区大小的补偿,其大小取决于着色区的大小,以及Slab与其每个对象的相对大小。但该区域与着色区的总和对于同一种对象的各个Slab是个常数。      每个对象的大小基本上是所需数据结构的大小。只有当数据结构的大小不与高速缓存中的缓冲行对齐时,才增加若干字节使其对齐。所以,一个Slab上的所有对象的起始地址都必然是按高速缓存中的缓冲行对齐的。   参考:深入分析Linux内核源码 http://oss.org.cn/kernel-book/      Memcached 源码分析

怎解决因碎片而造成内存分配速度降低的问题?

其实磁盘碎片应该称为文件碎片,是因为文件被分散保存到整个磁盘的不同地方,而不是连续地保存在磁盘连续的簇中形成的。

当应用程序所需的物理内存不足时,一般操作系统会在硬盘中产生临时交换文件,用该文件所占用的硬盘空间虚拟成内存。虚拟内存管理程序会对硬盘频繁读写,产生大量的碎片,这是产生硬盘碎片的主要原因。
其他如IE浏览器浏览信息时生成的临时文件或临时文件目录的设置也会造成系统中形成大量的碎片。文件碎片一般不会在系统中引起问题,但文件碎片过多会使系统在读文件的时候来回寻找,引起系统性能下降,严重的还要缩短硬盘寿命。另外,过多的磁盘碎片还有可能导致存储文件的丢失。
整理前的准备工作

我们在整理硬盘前一般都要对它清理垃圾信息,检查有无错误,最后才能谈到碎片的整理和优化。因此,我们在整理硬盘前,应该首先做好这些工作:

1、应该把硬盘中的垃圾文件和垃圾信息清理干净。系统工作一段时间后,垃圾文件就会非常之多,有程序安装时产生的临时文件、上网时留下的缓冲文件、删除软件时剩下的DLL文件或强行关机时产生的错误文件等,建议“菜鸟”朋友还是使用微软的“磁盘清理程序”代劳,“老鸟”当然可以使用一些功能更强的软件或手工清理。

2、检查并修复硬盘中的错误。首选的仍然是微软的“磁盘扫描程序”,虽然它的速度实在不怎么样,但只要你有足够的耐心,经过这个程序对磁盘完整而详细的扫描后,相信系统中的绝大多数错误已经被修复了。当然你也可以尝试一下其他工具,如扁鹊神医“Norton WinDoctor”,它的速度可比Windows中的“磁盘扫描工具”快多了。

六、整理方法及注意

在Windows里,用户可以从“开始”菜单中选择“程序/附件/系统工具/磁盘碎片整理程序”,弹出选择驱动器窗口,选择要整理的分区,然后点击[确定]即可开始整理,但此方法碎片整理过程非常耗时,一般2GB左右的分区需要1个小时以上,所以建议读者:

1、整理磁盘碎片的时候,要关闭其他所有的应用程序,包括屏幕保护程序,最好将虚拟内存的大小设置为固定值。不要对磁盘进行读写操作,一旦Disk Defragment发现磁盘的文件有改变,它将重新开始整理。

2、整理磁盘碎片的频率要控制合适,过于频繁的整理也会缩短磁盘的寿命。一般经常读写的磁盘分区一周整理一次。
 

怎解决碎片而造成内存分配速度降低的问题?

磁碟重组。真是郁闷。要麼就格式化,多简单。
 

相关内容