内存分配优化:碎片,速度与多线程支持,tcmalloc
内存碎片一般是由于空闲的连续空间比要申请的空间小,导致这些小内存块不能被利用,而形成内存碎片。
调用malloc函数时,它沿连接表寻找一个大到足以满足用户请求所需要的内存块。然后,将该内存块一分为二(一块的大小与用户请求的大小相等,另一块的大小就是剩下的字节)。接下来,将分配给用户的那块内存传给用户,并将剩下的那块(如果有的话)返回到连接表上。调用free函数时,它将用户释放的内存块连接到空闲链上。到最后,空闲链会被切成很多的小内存片段,如果这时用户申请一个大的内存片段,那么空闲链上可能没有可以满足用户要求的片段了。于是,malloc函数请求延时,并开始在空闲链上翻箱倒柜地检查各内存片段,对它们进行整理,将相邻的小空闲块合并成较大的内存块。
多次调用malloc()后空闲内存被切成很多的小内存片段,这就使得用户在申请内存使用时,由于找不到足够大的内存空间,malloc()需要进行内存整理,使得函数的性能越来越低。聪明的程序员通过总是分配大小为2的幂的内存块,而最大限度地降低潜在的malloc性能丧失。也就是说,所分配的内存块大小为4字节、8字节、16字节等。分配小而且生命周期很长的内存块时,才容易导致内存碎片的出现。
所以如果要动态分配的空间比较小,一般采取先分配一大块空间。然后在有内存分配需求时,从大块空间依次取出。
如果分配的空间很快就会释放(如分配释放同时在一个函数内),那么就不需要考虑内存碎片问题。
要避免内存分配中的碎片问题,一是尽量避免对malloc()函数的调用,能在栈上分配解决问题的,就不用malloc()来解决。
而在不得已非要频繁调用malloc()函数来分配内存的时候,就需要考虑内存池的技术了。内存池是池化技术中的一种,还包括连接池,线程池等。连接池用来解决频繁连接的问题,线程池用来解决频繁线程创建的问题,而内存池就是用来解决频繁内存分配产生内存碎片的问题。比如一开始直接用malloc申请一大段内存,做成一个内存池,然后再定义一组分配和释放接口对其进行管理,避免了频繁申请和释放内存。
tcmalloc:TCMalloc (google-perftools) 是用于优化C++写的多线程应用,比glibc 2.3的malloc快。这个模块可以用来让MySQL在高并发下内存占用更加稳定
tcmalloc就是一个内存分配器,管理堆内存,主要影响malloc和free,用于降低频繁分配、释放内存造成的性能损耗,并且有效地控制内存碎片。glibc中的内存分配器是ptmalloc2,tcmalloc号称要比它快。一次malloc和free操作,ptmalloc需要300ns,而tcmalloc只要50ns。同时tcmalloc也优化了小对象的存储,需要更少的空间。tcmalloc特别对多线程做了优化,对于小对象的分配基本上是不存在锁竞争,而大对象使用了细粒度、高效的自旋锁(spinlock)。分配给线程的本地缓存,在长时间空闲的情况下会被回收,供其他线程使用,这样提高了在多线程情况下的内存利用率,不会浪费内存,而这一点ptmalloc2是做不到的。
源码不需任何修改,tcmalloc会自动替换掉glibc默认的malloc和free
g++ test.cpp -ltcmalloc LINUX平台tcmalloc: Thread cached malloc