TCMalloc源码学习(一)
打算一边学习tcmalloc的源码一边写总结文章。先从转述TCMalloc的一篇官方文档开始(TCMalloc : Thread-Caching Malloc)。
为什么用TCMalloc
TCMalloc比glibc 2.3 的malloc(是一个单独的库叫做ptmalloc2)和其他测试过的malloc都要快。TCMalloc致力于减少多线程对锁的竞争,在获取小的内存对象时候基本上不需要锁,在获取大的内存对象时候,TCMalloc使用了细粒度的高效率的spinlocks。ptmalloc2当然也有做减少线程间对锁进行竞争的努力,它每一个线程都有一个areans,彼此互不关联,这样显然有个问题就是一个线程释放的内存另外一个线程无法使用,只能新申请内存,如此下去,内存只会暴涨了。TCMalloc另一个优点是非常高效的空间利用率,例如在分配N个8 bytes的objects内存的时候,大约最多只会有8N *0.01 bytes(其实是8N / 7),后面源码分析会解释)的overhead,相比之下ptmalloc则是每一个object用了4bytes头,overhead总共达到了N*4 bytes。
用法(略)
原理概述
TCMalloc给每一个线程也分配一个线程本地的cache。然后小对象的分配都是从这个cache中获取。而线程本地的cache又是按需从所有线程共享的一个central heap中获取的。周期的垃圾回收可以把部分内存从线程本地的cache挪回central heap,达到内存分配在多个线程中按需调度的目的。
TCMalloc对待大的内存(文章说大于32K,但其实看代码应该是大于256K的内存)的分配是直接从central heap中使用页层级的分配器(一页内存就是按照 4K 对齐的内存区域)分配的,也就是一个大的对象地址总是按页对齐横跨多个页。
在central heap中内存也是按页向系统请求分配的,而这些页内存通常会被裁剪成许多小对象,例如一页4K的内存能够被裁减成32个大小为128字节的objects。
小对象的分配
每一个小对象对应了一个大约有60项的map中的一项。每一项代表了包含这个size的内存,程序中用了一个链表把该size的可用的object串起来,总的来说这个map就是线程本地cahche。map中的表项按照尺寸划分的,对于前面小一点的size是每8个字节增加一项,大一点的就是16字节,具体的哪一个size对应哪一项,程序中有注释如下:
//
// Examples:
// Size Expression Index
// -------------------------------------------------------
// 0 (0 + 7) / 8 0
// 1 (1 + 7) / 8 1
// ...
// 1024 (1024 + 7) / 8 128
// 1025 (1025 + 127 + (120<<7)) / 128 129
// ...
// 32768 (32768 + 127 + (120<<7)) / 128 376
线程本地的cache每一个size对应的Index中放置的是一个linklist,叫做free list,把可用的object内存串起来了。
分配小对象的过程是:(1)找到要分配的size对应cache表中的那一项;(2)在线程本地的cache中即size对应的free list中寻找可用内存;(2)假如free list不是空的就取出第一个object返回,这个过程不需要锁;
假如free list是空的,则进行以下步骤:(1)从对应size项的central free list中(这个是所有线程共享的)提取一些对象内存;(2)提取到的一些对象内存放到对应的线程本地free list项中;(3)从新提取的一些对象中获取一个对象作为返回值。
假如central free list中可用内存也是空的,则(1)从central page allocator中分配出一些页内存;(2)把这些页内存划分成一个一个object内存;(3)把这些新的objects挂在对应的central free list上;(4)参照之前,从其中移动一些内存到线程本地free list中。
大对象的分配
大的内存分配是按页直接从central heap page获取的。central heap page也是一个free list数组,第n项代表数个n页内存串起来的链表。
- 分配k pages的内存就直接从数组的第k项的free list找可用内存,如果是空的就从下一项找,一直等到找到再返回,如果这样还是找不到,就要找系统去申请内存了(sbrk,mmap,。。。)。假如在一个大于k的free list中找到了空闲内存,多出来的内存要再插到数组合适的位置中去。
Spans
一个 Span 对象代表一些连续页内存,具体来说就是它在central heap page中是某一个free list中的节点,也是central free list某一个free list中的节点。在后者中,他还会把自己代表的页内存划分成对应size的一个一个objects,并用链表方式串起来,并等待线程本地cache去获取。
TCMalloc还有一个所有线程共享的数据结构就是一个map(文中叫做 central array),记录某一个页内存的页号具体对应哪一个span:
一个32位的系统共有4GB的虚拟地址空间,总共对应2^20 4K页,所以这个map最多占用4M的内存,看起来还可以接受。但是在64位的系统上就不行了,所以64位系统我们使用radix tree来代替数组实现这样的map。
内存释放
当释放一个内存对象的时候,我们算出他所在页的页号,然后在central array中找到该页号对应的Span,Span可以获取对应的sizeclass,然后可以知道是小内存对象还是大内存对象。假如是小块内存对象,把其插入线程本地的合适的free list中,假如线程本地cache超过了一个指定的大小(默认是2MB),垃圾收集器这时候会启动起来把没有在使用的objects从线程本地cache挪动到central free lists中。
假如是大块的内存对象,Span同时可以获取这个object占据的连续页内存,比如是占据了 [p,q]页,我们也会判断第p-1,q+1页,假如他们是空闲的,可以和[p,q]页合并,然后插入到central page heap的合适free list中。
管理小内存对象的Central Free Lists
Central Free Lists是一个Free list的链表数组,代表着每一个szie类别当前可用的内存,free list的每一个节点是Span,Span内部则又是一些objects构成的链表。
内存在线程本地的Cache和Central Free List之间的移动是以这些object为单位的,从Central Free list申请内存,会从中移动适量个数的object到线程本地Cache,线程本地Cache返还内存时,也会把这些objects放到Central Free list原来所在的Span中去。
当Span每分配出一个object就增加一个引用计数,每被返还一个object就释放一个引用计数,在返还时如果引用技术为0,即没有地方在引用这个Span了,这时就会把Span所占用的页直接释放回heap page。
线程本地Cache的垃圾回收
前面也已经提过,当一个线程本地的Cache其超过2MB后会被垃圾回收的。垃圾回收的这个阈值也会随着线程个数的增加而降低,因为这样可以减少应用程序对内存过度的使用。
垃圾回收时决定在一个free list中回收多少个内存对象是由每个size对应的free list的低水位线 L 控制的。L 记录了上次垃圾回收时这个free list的最小长度,每次回收 L/2 个内存对象。这种使用历史记录作为对未来预测的算法有比较好的效果,使得不同size的内存对象可以比较快速的在各个线程中交换使用。
TCMalloc源码学习(一)的更多相关文章
- TCMalloc源码学习(四)(小内存块释放)
pagemap_和pagemap_cache_ PageHeap有两个map,pagemap_记录某一内存页对应哪一个span,显然可能多页对应一个span,pagemap_cache_记录某一内存页 ...
- TCMalloc源码学习(二)
替换libc中的malloc free 不同平台替换方式不同. 基于unix的系统上的glibc,使用了weak alias的方式替换.具体来说是因为这些入口函数都被定义成了weak symbols, ...
- TCMalloc源码学习(三)(小块内存分配)
线程本地cache 线程本地cache对应的是类 ThreadCache,每一个thread一个实例,初始化代码在static函数CreateCacheIfNecessary中, 在该线程第一次申请内 ...
- Java集合专题总结(1):HashMap 和 HashTable 源码学习和面试总结
2017年的秋招彻底结束了,感觉Java上面的最常见的集合相关的问题就是hash--系列和一些常用并发集合和队列,堆等结合算法一起考察,不完全统计,本人经历:先后百度.唯品会.58同城.新浪微博.趣分 ...
- jQuery源码学习感想
还记得去年(2015)九月份的时候,作为一个大四的学生去参加美团霸面,结果被美团技术总监教育了一番,那次问了我很多jQuery源码的知识点,以前虽然喜欢研究框架,但水平还不足够来研究jQuery源码, ...
- MVC系列——MVC源码学习:打造自己的MVC框架(四:了解神奇的视图引擎)
前言:通过之前的三篇介绍,我们基本上完成了从请求发出到路由匹配.再到控制器的激活,再到Action的执行这些个过程.今天还是趁热打铁,将我们的View也来完善下,也让整个系列相对完整,博主不希望烂尾. ...
- MVC系列——MVC源码学习:打造自己的MVC框架(三:自定义路由规则)
前言:上篇介绍了下自己的MVC框架前两个版本,经过两天的整理,版本三基本已经完成,今天还是发出来供大家参考和学习.虽然微软的Routing功能已经非常强大,完全没有必要再“重复造轮子”了,但博主还是觉 ...
- MVC系列——MVC源码学习:打造自己的MVC框架(二:附源码)
前言:上篇介绍了下 MVC5 的核心原理,整篇文章比较偏理论,所以相对比较枯燥.今天就来根据上篇的理论一步一步进行实践,通过自己写的一个简易MVC框架逐步理解,相信通过这一篇的实践,你会对MVC有一个 ...
- MVC系列——MVC源码学习:打造自己的MVC框架(一:核心原理)
前言:最近一段时间在学习MVC源码,说实话,研读源码真是一个痛苦的过程,好多晦涩的语法搞得人晕晕乎乎.这两天算是理解了一小部分,这里先记录下来,也给需要的园友一个参考,奈何博主技术有限,如有理解不妥之 ...
随机推荐
- [leetcode712]204. Count Primes寻找范围内的素数
厄拉多塞筛选法,就是哈希表记录素数的倍数 public int countPrimes(int n) { /* 牛逼哄哄的厄拉多塞筛选法 就是从2开始,每找到一个素数,就把n以内的这个数的倍数排除 记 ...
- [leetcode]242. Valid Anagram判断两个字符串是不是包含相同字符的重排列
/* 思路是判断26个字符在两个字符串中出现的次数是不是都一样,如果一样就返回true. 记住这个方法 */ if (s.length()!=t.length()) return false; int ...
- 7.24,《C Primer Plus》复习第十五章第二小题
编写一个程序,通过命令行参数读取两个二进制字符串,对这两个二进制数使用~运算符,&运算符.|运算符,并以二进制字符串形式打印结果(如果无法使用命令行环境,可以通过交互式让程序读取字符串) 编写 ...
- 我的开源项目在五个月内超过了 600 star
其实我在 2016 年年底就开始写了这个项目:Forest,一个能够将 HTTP 的所有请求信息(包括 URL .Header 以及 Body 等信息)绑定到您自定义的 Interface 方法上,能 ...
- [ABP教程]第七章 作者:数据库集成
Web开发教程7 作者:数据库集成 关于此教程 在这个教程系列中,你将要构建一个基于ABP框架的应用程序 Acme.BookStore.这个应用程序被用于甘丽图书页面机器作者.它将用以下开发技术: E ...
- i5 11300H 怎么样 相当于什么水平
i5-11300H 为 4 核 8 线程,主频 3.1GHz,睿频 4.4GHz,三级缓存 8MBi5-11300H 怎么样看完你就知道了 https://list.jd.com/list.html?
- 结合MATLAB、Python、R语言,在求得显著差异的边(节点对)之后,怎么画circle图
先来看看成果图: OK,开始画图: 实验背景声明:在脑影像分析中,我们首先构建脑网络,然 ...
- 一个上传图片,预览图片的小demo
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...
- 剑指offer 面试题6:从尾到头打印链表
题目描述 输入一个链表,按链表值从尾到头的顺序返回一个ArrayList. 编程思想 从前往后遍历,将值存入栈中,然后打印栈中内容即可. 编程实现 /** * struct ListNode { * ...
- 【Linux】kali 安装 python3 和 pip3(亲测有效)
[Linux]kali 安装 python3 和 pip3 引言: 在使用kali的时候,经常会用到各种工具以及脚本,而大多数脚本都是以python编写的,但是烦就烦在python有2个版本,有些 ...