ptmalloc、tcmalloc与jemalloc对比分析(二)

简介: ptmalloc、tcmalloc与jemalloc对比分析(二)

从作用来看:

  • Fast bins 可以看着是small bins的一小部分cache,主要是用于提高小内存的分配效率,虽然这可能会加剧内存碎片化,但也大大加速了内存释放的速度!
  • Unsorted bin 可以重新使用最近 free 掉的 chunk,从而消除了寻找合适 bin 的时间开销,进而加速了内存分配及释放的效率。
  • Small bins 相邻的 free chunk 将被合并,这减缓了内存碎片化,但是减慢了 free 的速度;
  • Large bin 中所有 chunk 大小不一定相同,各 chunk 大小递减保存。最大的 chunk 保存顶端,而最小的 chunk 保存在尾端;查找较慢,且释放时两个相邻的空闲 chunk 会被合并。

其中fastbins保存在malloc_state结构的fastbinsY变量中,其他三者保存在malloc_state结构的bins变量中。

Chunk说明

一个 arena 中最顶部的 chunk 被称为「top chunk」。它不属于任何 bin 。当所有 bin 中都没有合适空闲内存时,就会使用 top chunk 来响应用户请求。当 top chunk 的大小比用户请求的大小小的时候,top chunk 就通过 sbrk(main arena)或 mmap( thread arena)系统调用扩容。

「last remainder chunk」即最后一次 small request 中因分割而得到的剩余部分,它有利于改进引用局部性,也即后续对 small chunk 的 malloc 请求可能最终被分配得彼此靠近。当用户请求 small chunk 而无法从 small bin 和 unsorted bin 得到服务时,分配器就会通过扫描 binmaps 找到最小非空 bin。正如前文所提及的,如果这样的 bin 找到了,其中最合适的 chunk 就会分割为两部分:返回给用户的 User chunk 、添加到 unsorted bin 中的 Remainder chunk。这一 Remainder chunk 就将成为 last remainder chunk。当用户的后续请求 small chunk,并且 last remainder chunk 是 unsorted bin 中唯一的 chunk,该 last remainder chunk 就将分割成两部分:返回给用户的 User chunk、添加到 unsorted bin 中的 Remainder chunk(也是 last remainder chunk)。因此后续的请求的 chunk 最终将被分配得彼此靠近。

问题
  • 如果后分配的内存先释放,无法及时归还系统。因为 ptmalloc 收缩内存是从 top chunk 开始,如果与 top chunk 相邻的 chunk 不能释放, top chunk 以下的 chunk 都无法释放。
  • 内存不能在线程间移动,多线程使用内存不均衡将导致内存浪费
  • 每个chunk至少8字节的开销很大
  • 不定期分配长生命周期的内存容易造成内存碎片,不利于回收。
  • 加锁耗时,无论当前分区有无耗时,在内存分配和释放时,会首先加锁。

从上述来看ptmalloc的主要问题其实是内存浪费、内存碎片、以及加锁导致的性能问题。

备注:glibc 2.26( 2017-08-02 )中已经添加了tcache(thread local cache)优化malloc速度

tcmalloc

tcmalloc是Google开发的内存分配器,在Golang、Chrome中都有使用该分配器进行内存分配。有效的优化了ptmalloc中存在的问题。当然为此也付出了一些代价,按下不表,先看tcmalloc的具体实现。

系统向看tcmalloc内存管理

tcmalloc把8kb的连续内存称为一个页(Page),可以用下面两个常量来描述:

const size_t kPageShift = 13;

const size_t kPageSize = 1 << kPageShift;

对于一个指针p,p>>kPageShift即是p的页地址。同样的对于一个页地址x,管理的实际内存区间是[x <<kPageShift, (x+1)<<kPageShift)。一个或多个连续的页组成一个Span.对于一个Span,管理的实际内存区间是[start<<kPageShift, (start+length)<<kPageShift)。tcmalloc中所有页级别的操作,都是对Span的操作。PageHeap是一个全局的用来管理Span的类。PageHeap把小于的空闲Span保存在双向循环链表上,而大的span则保存在SET中。保证了所有的内存的申请速度,减少了内存查找。

// Information kept for a span (a contiguous run of pages).
struct Span {
  PageID        start;          // Starting page number
  Length        length;         // Number of pages in span
  Span*         next;           // Used when in link list
  Span*         prev;           // Used when in link list
  union {
    void* objects;              // Linked list of free objects
    // Span may contain iterator pointing back at SpanSet entry of
    // this span into set of large spans. It is used to quickly delete
    // spans from those sets. span_iter_space is space for such
    // iterator which lifetime is controlled explicitly.
    char span_iter_space[sizeof(SpanSet::iterator)];
  };
  unsigned int  refcount : 16;  // Number of non-free objects
  unsigned int  sizeclass : 8;  // Size-class for small objects (or 0)
  unsigned int  location : 2;   // Is the span on a freelist, and if so, which?
  unsigned int  sample : 1;     // Sampled object?
  bool          has_span_iter : 1; // If span_iter_space has valid
                                   // iterator. Only for debug builds.
  // What freelist the span is on: IN_USE if on none, or normal or returned
  enum { IN_USE, ON_NORMAL_FREELIST, ON_RETURNED_FREELIST };
};
// We segregate spans of a given size into two circular linked
// lists: one for normal spans, and one for spans whose memory
// has been returned to the system.
struct SpanList {
Span        normal;
Span        returned;
};
// Array mapping from span length to a doubly linked list of free spans
//
// NOTE: index 'i' stores spans of length 'i + 1'.
SpanList free_[kMaxPages];
// Sets of spans with length > kMaxPages.
//
// Rather than using a linked list, we use sets here for efficient
// best-fit search.
SpanSet large_normal_;
SpanSet large_returned_;


目录
相关文章
|
存储 缓存 算法
【C/C++ 性能优化】提高C++程序的缓存命中率以优化性能
【C/C++ 性能优化】提高C++程序的缓存命中率以优化性能
2297 0
|
Linux API 调度
深入了解DPDK:如何优化网络包处理性能(上)
深入了解DPDK:如何优化网络包处理性能
深入了解DPDK:如何优化网络包处理性能(上)
|
存储 Java 测试技术
JAVA-MAVEN初学者教程(配置、pom.xml、依赖管理等)
JAVA-MAVEN初学者教程(配置、pom.xml、依赖管理等)
3849 0
|
Unix 关系型数据库 MySQL
|
数据采集 自动驾驶 Java
PAI-TurboX:面向自动驾驶的训练推理加速框架
PAI-TurboX 为自动驾驶场景中的复杂数据预处理、离线大规模模型训练和实时智能驾驶推理,提供了全方位的加速解决方案。PAI-Notebook Gallery 提供PAI-TurboX 一键启动的 Notebook 最佳实践
|
7月前
|
搜索推荐 数据库 索引
广告引擎的整体架构和工作过程
广告引擎核心是匹配用户与广告。通过用户标签、广告位信息及广告主定向条件,构建倒排索引,实现高效召回与排序,0.1秒内完成广告返回,并实时监测展现、点击与计费,确保精准投放与预算控制。
|
10月前
|
缓存 监控 安全
Spring Boot 的执行器注解:@Endpoint、@ReadOperation 等
Spring Boot Actuator 提供多种生产就绪功能,帮助开发者监控和管理应用。通过注解如 `@Endpoint`、`@ReadOperation` 等,可轻松创建自定义端点,实现健康检查、指标收集、环境信息查看等功能,提升应用的可观测性与可管理性。
483 0
Spring Boot 的执行器注解:@Endpoint、@ReadOperation 等
|
缓存 NoSQL 中间件
Redis的线程模型
Redis采用单线程模型确保操作的原子性,每次只执行一个操作,避免并发冲突。它通过MULTI/EXEC事务机制、Lua脚本和复合指令(如MSET、GETSET等)保证多个操作要么全成功,要么全失败,确保数据一致性。Redis事务在EXEC前失败则不执行任何操作,EXEC后失败不影响其他操作。Pipeline虽高效但不具备原子性,适合非热点时段的数据调整。Redis 7引入Function功能,支持函数复用,简化复杂业务逻辑。总结来说,Redis的单线程模型简单高效,适用于高并发场景,但仍需合理选择指令执行方式以发挥其性能优势。
388 6
|
人工智能 编解码 算法
Doubao-1.5-pro:字节跳动最新豆包大模型,性能超越GPT-4o和Claude 3.5 Sonnet
豆包大模型1.5是字节跳动推出的最新大模型,采用大规模稀疏MoE架构,支持多模态输入输出,具备低时延语音对话能力,综合性能优于GPT-4o和Claude 3.5 Sonnet。
3028 2
Doubao-1.5-pro:字节跳动最新豆包大模型,性能超越GPT-4o和Claude 3.5 Sonnet
|
存储 固态存储 Windows
兼容性
兼容性
941 2