青训营笔记4 -Go 语言内存管理详解

113 阅读5分钟

青训营笔记4 -Go 语言内存管理详解

这是我参与「第五届青训营」伴学笔记创作活动的第 4 天

概述

本节课程主要分为四个方面:

  1. 性能优化
  1. 自动内存管理
  1. Go 内存管理及优化
  1. 编译器和静态分析
  1. Go编译器优化

知识点介绍

性能优化

性能优化是提高软件系统处理能力,减少不必要的消耗,充分发掘计算机算力,能够带来用户体验的提升和资源的高效利用。下面我们来了解一下性能优化的层面:

  • 业务层优化: 针对特定场景,具体问题,具体分析,一般收益较大

  • 语言运行时的优化: 对GO的SDK进行语言运行时的优化,解决更通用的性能问题,考虑更多场景

  • 数据驱动: 使用像pprof这样的自动化性能分析工具,优化是依靠数据而非猜测,首先优化最大瓶颈

性能优化的软件质量至关重要,要在保证接口稳定的前提下改进具体实现,同时要有能覆盖尽可能多的场景的测试用例,方便回归测试。文档要准确描述软件软件做了什么没做什么,有什么样的效果,同时可以观测。

自动内存管理

自动内存管理也就是我们常说的垃圾回收,是程序在运行时根据需求动态的管理内存的一种机制。可以避免我们去手动的管理内存,专注于实现业务逻辑,同时保证内存使用的正确性和安全性。

下面我们来了解一下自动内存管理中的相关概念:

  • Mutator: 业务线程,分配新对象,修改对象指向关系
  • Collector: GC 线程,找到存活对象,回收死亡对象的内存空间
  • Serial GC: 只有一个 collector
  • Parallel GC: 并行 GC,支持多个 collectors 同时回收的 GC 算法
  • Concurrent GC: 并发 GC,支持 mutator(s) 和 collector(s) 同时执行的 GC 算法。

要评价GC算法,基本要求是安全性,不能够回收存活的对象。在自动内存管理中,我们要求Collector必须能够感知对象指向关系的改变,这样才能够保证自动内存回收的安全性和准确性。

追踪垃圾回收

追踪垃圾回收的基本流程如下:

  1. 标记根对象:静态变量、全局变量、常量、线程栈等
  1. 标记:找到可达对象
  1. 清理:所有不可达对象

引用计数

引用计数的垃圾回收机制大致流程如下:

在程序运行时,对每一个对象都添加一个相关的引用数目,只要有引用指向这个对象就说明这个对象是存活的。

这种方法的优点在于内存管理的操作与程序执行的过程同时进行,内存管理不需要了解运行时的实现细节。

但是同时也存在着如下的缺点:

  • 引用计数开销比较大
  • 无法对环形数据结构进行回收
  • 当我们同时释放大量内存需要对其进行回收时,依然可能发生暂停。

Go内存管理

Go对象分配是非常高频的操作,小对象占比比较高,非常耗时,优化方案:Balanced GC,本质上是将多个小对象分配合并成一次大对象的分配,但是GAB的对象分配方式会导致内存被延迟释放。对此,可以通过移动GAB中存活的对象来避免,用copyingGC的算法来管理小对象。

编译器和静态分析

静态分析

静态分析不执行程序的行为,推导程序的行为,分析程序的性质,一般分为控制流分析和数据流分析,通过分析控制流和数据流,我们可以知道更多关于程序的性质,从而根据这些性质来改代码。

静态分析

过程间分析是指仅在过程内部进行分析,过程间分析则考虑过程调用时参数传递和返回值的数据流和控制流,需要同时分析数据流和数据流

Go编译器优化

函数内联(inlinging)

函数内联将被调用函数(callee)的副本替换到调用位置(caller)上,同时重写代码以反映参数的绑定,大多数情况下是正向优化。这种方法的优点在于消除函数调用开销,例如传递参数、保存寄存器等,同时将过程间分析转化为过程内分析,帮助其他优化,例如逃逸分析。它存在的缺点时函数体变大,对instruction(icahe)不友好,编译产生的Go镜像变大。

逃逸分析

逃逸分析的对象是代码中指针的动态作用域,即指针在何处可以被访问,大致思路如下:

  • 从对象分配处出发,沿着控制流,观察对象的数据流
  • 若发现指针p在当前作用域S:
    • 作为参数传递给其他函数
    • 传递给全局变量
    • 传递给其他的goroutine
    • 传递给已逃逸的指针指向的对象
  • 则指针p指向的对象逃逸出s,反之则没有逃逸出s Beast mode函数内联拓展了函数对象,更多对象不逃逸,未逃逸的对象可以实现栈上分配。