Go语言——性能优化及自动内存管理 | 青训营笔记

620 阅读5分钟

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

前言

本文主要介绍:

  • 性能优化
  • 内存管理以及自动内存管理优化

性能优化

1.性能优化的定义与原因

定义:提升软件系统的处理能力,减少不必要的消耗,充分发掘计算机算力。

原因:

  1. 用户体验:带来用户体验的提升

    • 抖音滑动点击更加顺滑流畅
    • 突发事件618,双11,双12购物能够尽可能做到和平时购物一样的流畅度,不卡顿
  2. 资源高效利用:降低成本,提高效率

    • 很小的优化乘以海量机器会显著的性能提升和成本节约

2.两个层面

业务层优化:

  • 针对特定场景,具体问题,具体分析
  • 容易获得较大性能提升

语言运行优化:

  • 解决更通用的性能问题
  • 需要考虑更多的场景
  • Tradeoffs,折中考虑

3.可维护性

一般来说,优化都是需要写代码,而一般性能优化都是对Go SDK写代码

Go SDK

  • 接口层:Commands APIs New APIs
  • 实现层: Compiler Scheduler GC Runtime Libs Profiling

质量和可维护性要保证

  1. 前提:保证接口稳定再进行改进
  2. 测试用例:覆盖尽可能多的场景,基于正确的软件进行优化
  3. 文档:将做了什么,能达到什么效果展示给用户,以便用户的个性化使用
  4. 隔离:通过选项控制是否开启优化
  5. 可观测:日志输出,告诉用户功能是正常的

内存管理优化

自动内存管理

  • 动态内存

    • 程序在运行时根据需求动态分配的内存:malloc()
  • 自动内存管理(垃圾回收):由程序语言的运行时系统管理动态内存

    • 避免手动管理内存,专注于实现业务逻辑
    • 保证内存使用的正确性安全性:double-free problem,use-after-free problem
  • 对于自动内存管理:三个任务

    • 为新对象分配空间
    • 找到存活对象
    • 回收死亡对象的内存空间

相关概念:

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

image.png

其中,Collectors必须感知对象指向关系的改变

原因见图,

image.png

GC算法的评价指标:

  • 安全(Safety):基本要求,不能回收存货的对象

  • 吞吐率(throughput):花在gc上的时间,1-GC_time/total_time

  • 暂停时间(pause time): STW(stop the world) 业务是否感知

  • 内存开销(space overhead) :GC 元数据开销

  • 追踪垃圾回收(tracing garbage collection)

    • 对象被回收的条件:指针指向关系不可达的对象

    • 标记根对象,包括全局变量global、堆heap、栈stack,线程栈等

    • 标记:找到可达对象

      • 传递闭包,从根对象出发找所有可达对象
    • 清理:不可达对象,包括三种方式:

      1. 将存活对象复制到另外的内存空间(Copying GC)。

        • 需要另外的内存空间
      2. 将死亡对象的内存标记为"可分配”(Mark-sweep GC)。

        • 通过free list 管理空闲内存,分配找free list一个内存块即可
      3. 移动并整理存活对象(Mark-compact GC)

        • 原地整理对象(进行压缩,copy在最开始地方)
  • 分代GC(generational GC)

    • 分代假说:most objects die young

    • 思想:很多对象分配出来之后很快就不用了。

    • 每个对象都有一个年龄:与经过GC的次数相关

    • 不同年龄的对象分配在不同heap区域。

    • 目的:通过制定不同的GC策略,降低整体内存管理的开销

    • 对于年轻代

      • 常规的对象分配
      • 由于一般来说年轻代存活对象很少,用Copying GC处理
      • gc吞吐率很高
    • 对于老年代

      • 对象趋近一直或者,反复赋值开销较大
      • 可以采用Mark-compact GC处理
  • 引用计数(Reference counting)

    • 每个对象都有一个与之关联的引用数目

    • 圈圈内部数字为引用数目,当引用数目为0时会被清理

    • image.png

    • 条件(思路):当且仅当引用数大于0时对象存活。

    • 优点:

      • 内存管理的操作被平摊到程序执行过程中(程序执行过程中就有内存管理)
      • 程序进行过程中维护对象的计数,甚至不需要了解runtime的实现细节,如C++智能指针smart pointer
    • 缺点:

      • 维护上述引用计数的开销比较大:通过原子操作保证引用计数操作的原子性可见性
      • 无法回收环形数据结构,可以通过weak reference解决这个问题
      • 内存开销:每个对象都引入的额外内存空间存储引用数目,会使得内存开销变大
      • 大数据结构在回收内存时依然可能引发暂停

心得

了解性能优化的目的和意义,学习了内存管理以及自动内存管理的相关知识,获益匪浅,都是学校很少细讲的知识。通过从大的自动内存管理观念讲到细小的gc算法,慢慢带你由浅入深地揭开内存管理的面纱。

引用

ppt:‌⁤​⁢⁤‬⁣⁣⁡‌⁡⁡⁢⁢⁢‬⁢⁤⁤‬‍‬​⁤​‌​‍⁣​‬⁣⁡​‬⁣⁢​⁤⁣‍‌‌⁤​​⁤高性能 Go 语言发行版优化与落地实践 .pptx - 飞书云文档 (feishu.cn)