注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

gmd20的个人空间

// 编程和生活

 
 
 

日志

 
 

malloc内存分配大小有别  

2014-08-22 21:38:09|  分类: 程序设计 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
写个简单的循环,不停的new[] 和delete[]  内存,你就会发现,使用默认glibc里面malloc的时候,当一次分配的内存块比较大的时候比如几百kb或者 几MB,性能要差很多。
  for(int i=0;i<1000*10*1;i++){
    int s1=(i%9?:9)*1024*1024;
    char * p1= new char [s1]
    memset(p1,0,s1);
    delete [] p1;
  }

使用strace观察可以看到每次 new [] 和delete都有mmap和munmap调用。
mmap2(NULL, 2101248, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb72af000
munmap(0xb72af000, 2101248)             = 0
mmap2(NULL, 3149824, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb71af000
munmap(0xb71af000, 3149824)   

用time计时,可以发现大量的开销发生在内核空间
bright@ubuntu:~/test$ time ./test 
real 0m11.191s
user 0m3.752s
sys 0m7.432s

使用perf record ./test  , perf report  可以证实了这点,clear_huge_page , get_page_from_freelist很频繁,甚至很多
__do_page_fault 缺页现象。
 31.53%  test  libc-2.19.so         [.] __memset_sse2
 16.54%  test  [kernel.kallsyms]    [k] clear_huge_page
  9.04%  test  [kernel.kallsyms]    [k] get_page_from_freelist
  6.62%  test  [kernel.kallsyms]    [k] __do_page_fault
  4.49%  test  [kernel.kallsyms]    [k] native_flush_tlb_single
  3.07%  test  [kernel.kallsyms]    [k] handle_mm_fault
  3.05%  test  [kernel.kallsyms]    [k] __kunmap_atomic
  2.74%  test  [kernel.kallsyms]    [k] __do_softirq
  2.14%  test  [kernel.kallsyms]    [k] kmap_atomic_prot
  1.91%  test  [kernel.kallsyms]    [k] _raw_spin_unlock_irqrestore
  1.60%  test  [kernel.kallsyms]    [k] free_hot_cold_page
  1.56%  test  [kernel.kallsyms]    [k] __mem_cgroup_try_charge
  1.23%  test  [kernel.kallsyms]    [k] __alloc_pages_nodemask
  0.99%  test  [kernel.kallsyms]    [k] __mem_cgroup_count_vm_event
  0.86%  test  [kernel.kallsyms]    [k] __mem_cgroup_commit_charge

也就是说,malloc一个比较大的内存的时候,每次都是直接用mmap 和mumap直接申请用户内存空间映射,每次都要内核参与。  glibc不会对大内存块做缓存管理。每次重新映射是很低效的,又有缺页异常。
如果每次分配和删除小内存块比如几kb,几十几百字节的是没有这个问题的。  当使用其他内存分配器比如tcmalloc的时候也没有这个频繁mmap mumap问题的。

查看一下glibc的malloc文件里面注释,明确写了,如果大于 128KB的内存分配是直接依赖于系统内存映射mmap操作来管理的,没有小内存块的内存池管理等快速分配算法了。
https://github.com/lattera/glibc/blob/master/malloc/malloc.c
  The main properties of the algorithms are:
  * For large (>= 512 bytes) requests, it is a pure best-fit allocator,
    with ties normally decided via FIFO (i.e. least recently used).
  * For small (<= 64 bytes by default) requests, it is a caching
    allocator, that maintains pools of quickly recycled chunks.
  * In between, and for combinations of large and small requests, it does
    the best it can trying to meet both goals at once.
  * For very large requests (>= 128KB by default), it relies on system
    memory mapping facilities, if supported.

其实这也是非常合理的,程序里面不应该写频繁的分配这种大块内存的代码,这么大的比如1兆的内存块,要跨多个page,占了很多的虚拟空间,管理起来是不太容易的,如果是32位程序不释放可能都很快占用完物理内存了。 伙伴系统应该也适合于这么大的内存块吧。

其实其他内存管理器比如tcmalloc也会对这种小块内存和大量内存分别对待的。
看看tcmalloc的说明
http://goog-perftools.sourceforge.net/doc/tcmalloc.html
TCMalloc treates objects with size <= 32K ("small" objects) differently from larger objects. Large objects are allocated directly from the central heap using a page-level allocator (a page is a 4K aligned region of memory). I.e., a large object is always page-aligned and occupies an integral number of pages.

Small Object Allocation

小内存分配,直接根据内存块size映射到170个类别的单项链表里面去分配。 小内存的都是thread-local cache里面直接分配,不存在锁竞争。

Large Object Allocation

大内存块(> 32K) 的会按照page 4k取整,直接从共用的全局central page heap 里面分配
1 到255个连续的page大小,都有对应的单向链表,类似小内存块的链表管理。

可以看到tcmalloc对大的内存块也提供了一定的缓存机制,但还是需要额外的开销管理和回收page的。这个开销是比小内存块的要大的。还有小内存块的分配是没有锁竞争的(只有thread local cache里面空闲用完了,需要从central page heap再次获取空闲对象是才需要锁,而且那个是批量操作。当然垃圾回收把多余的内存重新合并到central page heap应该也需要锁,但这个和分配一样是可以忽略不计的),但这个大内存块都到全局的central page heap分配是有竞争的。

不过tcmalloc的 大内存块没有glibc的每次mmap/munmap和缺页,还是要快的多的。



总结一下:
        不要频繁分配几百kb (tcmalloc是32kb)的内存,这个性能很差,不过一般程序一般也不会这么写吧。
适当的时候自己使用内存池管理避免重复分配是有好处的,比如对大内存块来说。
       之前测试也发现,当内存空间比较紧张时,windows 7 内存管理器的开销也是会很大的,普通的new 和delete会突然变得很慢。  要注意程序的内存使用和分配和释放这些才行。
  评论这张
 
阅读(484)| 评论(0)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017