go内存管理与优化

62 阅读4分钟

一、Go 内存分配 - 分块

  1. 基本原理

    • 内存申请:Go 程序启动时,会通过系统调用mmap()向操作系统申请一大块内存,例如 4 MB。这是内存分配的基础。
    • 划分大块:将申请到的大内存划分成较大的块,如 8 KB 大小的块,这些块被称为mspanmspan是 Go 内存管理中的重要结构。
    • 细分小块:每个mspan会进一步被划分成更小的特定大小的块,用于实际的对象分配。这些小块的大小根据对象的大小需求而定。
  2. mspan类型

    • noscan mspan

      • 用于分配不包含指针的对象。在垃圾回收(GC)过程中,这些对象不需要被扫描,因为它们不包含指针,不会引起引用关系的追踪问题。
    • scan mspan

      • 用于分配包含指针的对象。在GC时,需要扫描这些对象,以处理对象之间的引用关系,确保内存的正确回收和对象的存活判断。
  3. 对象分配策略

    • 根据对象的大小,从已划分好的小块中选择最合适的块进行分配。这种策略可以减少内存碎片,提高内存利用率。

二、Go 内存分配 - 缓存

  1. TCMalloc机制

    • 线程缓存:在 Go 中,采用了类似TCMalloc(Thread - Caching Malloc)的机制。每个处理器(p)都有一个本地的mcache用于快速分配内存。
    • 协程分配mcache用于为绑定在该处理器上的协程(g)分配对象。当协程需要分配内存时,首先会在本地的mcache中查找可用的mspan
  2. mspan缓存管理

    • mcachemcentral的交互:当mcache中的mspan分配完毕后,会向mcentral申请带有未分配块的mspan
    • 缓存机制:当mspan中没有分配的对象时,mspan会被缓存在mcentral中,而不是立即释放并归还给操作系统。这种缓存机制可以减少内存分配和释放的开销。

三、Go 内存管理优化

  1. 堆内存分配的尺寸分布

    • 分析内存分配频率:通过分析堆内存分配的尺寸分布,可以了解不同大小的对象在内存分配中的频率。通常会发现小对象的分配频率较高。
    • 优化方向:由于小对象占比较高,优化小对象的分配和管理可以显著提高内存管理的效率。
  2. 对象分配的性能问题

    • 高频操作:对象分配在 Go 程序中是非常高频的操作,每秒可能会分配 GB 级别的内存。
    • 小对象居多:小对象在内存分配中占比较高,而小对象的分配和管理可能会带来更多的开销。
    • 分配路径长:Go 内存分配的路径较长,涉及到协程(g)、处理器(p)、mcachemspan等多个层次的查找和分配操作,这导致了内存分配比较耗时。
    • pprof分析pprof工具可以用于分析对象分配的性能,通常会发现对象分配函数是最频繁调用的函数之一,这也是优化的重点方向。

四、Balanced GC(平衡垃圾回收)

  1. goroutine allocation buffer (GAB)

    • 基本概念:每个协程(g)都绑定一大块内存(如 1 KB),称为GABGAB主要用于分配小于 128 B 的noscan类型小对象。
    • 指针管理:使用baseendtop三个指针来维护GAB。采用Bump pointer(指针碰撞)风格进行对象分配,这种方式简单高效,不需要与其他分配请求互斥。
  2. Balanced GC的问题与解决方案

    • 问题

      • GAB本身对于 Go 内存管理来说是一个对象,它将多个小对象的分配合并成一次这个对象的分配。这种方式可能会导致内存被延迟释放。
    • 解决方案

      • 对象移动:当GAB的总大小超过一定阈值时,将GAB中存活的对象复制到另外分配的GAB中,原GAB可以释放,避免内存泄漏。
      • 算法本质:这种方式本质上是采用copying GC(复制式垃圾回收)的算法来管理小对象。根据对象的生命周期,使用不同的标记和清理策略,提高垃圾回收的效率。

五、Balanced GC - 性能收益

  1. 性能提升数据

    • 通过采用Balanced GC,在高峰期可以使CPU usage降低 4.6%,核心接口时延下降 4.5% - 7.7%。这表明Balanced GC在优化内存管理和垃圾回收方面取得了显著的性能提升。

通过这些机制和优化策略,Go 语言在内存管理和垃圾回收方面能够在兼顾易用性的同时,实现高效的性能表现。