高性能 GO 语言发行版优化与落地实践 | 青训营笔记

164 阅读4分钟

高性能 GO 语言发行版优化与落地实践 | 青训营笔记

这是我参与 「第三节青训营-后端场」笔记创作活动的第四篇

高性能 GO 语言发行版优化与落地实践

追求极致性能

性能优化是什么?

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

为什么要进行性能优化

  1. 用户体验:带来用户体验的提升
  2. 资源高效利用:降低成本,提高效率-很少的优化乘以海量机器会是显著的性能提升和成本节约

性能优化的层面

  1. 软件结构:业务代码->SDK->基础库->语言运行时->OS
  2. 业务层的优化
    1. 针对特定场景,具体问题,具体分析
    2. 容易获得较大性能收益
  3. 语言运行时优化
    1. 解决更通用的性能问题
    2. 考虑更多场景
    3. Tradeoffs
  4. 数据驱动
    1. 自动化性能分析工具--pprof
    2. 依靠数据而非猜测
    3. 首先优化最大瓶颈

Go SDK

在保证接口稳定的前提下改进具体实现

接口

  1. Commands
  2. APIs
  3. New APIs

实现

  1. Comoiler
  2. Scheduler
  3. GC
  4. Runtime
  5. Libs
  6. Profiling

自动内存管理

背景

动态内存

​ 程序在运行时根据需要动态分配的内存malloc()

自动内存管理(垃圾回收)由程序语言的运行时系统回收动态内存

三个任务

  1. 为新对象分配空间
  2. 找到存活对象
  3. 回收死亡对象

相关概念

Mutator:业务线程,分配新对象,修改对象指向关系

Collector:GC线程,找到存活对象,回收死亡对象的内存空间

Serial:只有一个collector

Parallel GC:支持多个collectors同时回收的GC算法

Concurrent GC:mutator和collector可以同时执行

Collectors必须感知对象指向关系的改变!

GC算法

安全性:不能回收存活的对象基本要求

吞吐率:1-GC时间/程序执行时间,花在GC上的时间

暂停时间:业务是否感知

内存开销:GC元数据开销

追踪垃圾回收

  1. 对象被回收的条件:指针指向关系不可达的对象
  2. 标记跟对象:静态变量,全局变量,常量,线程栈等
  3. 标记:找到可达对象
  4. 清理:不可达对象

分代GC-常用的内存管理机制

  1. 每个对象都有年龄:经过GC的次数
  2. 目的:对年轻和老年的对象,制定不同的GC策略,降低整体内存管理的开销

年轻代

  1. 常规的对象分配
  2. 由于存活对象很少,可以使用copying collection
  3. GC吞吐率很高

老年代

  1. 对象趋向于一直活着,反复复制开销较大
  2. 可以采用mark-sweep collection

引用计数

  1. 每个对象都有一个与之相关的引用计数
  2. 对象存活的条件:当且仅当引用计数大于
  3. 优点
    1. 内存管理的操作被平摊到程序执行过程中
    2. 内存管理不需要了解runtime的实现细节

Go内存管理及优化

Go内存分配--分块

  1. 目标:为对象再heap上分配内存
  2. 提前将内存分块
    1. 调用系统调用mmap()向OS申请一大块内存
    2. 先将内存划分为大块
    3. 再将大块继续划分为特定大小的小块,用于对象分配
    4. noscan mspan:分配不包含指针的对象 ——GC不需要扫描
    5. scan mspan : 分配包含指针的对象——GC需要扫描

Go内存管理优化

  1. 对象分配是非常高频的操作:每秒分配GB级别的内存
  2. 小对象占比较高
  3. Go内存分配比较耗时

编译器和静态分析

编译器结构

  1. 重要的系统软件
    1. 识别符合语法和非法的程序
    2. 生成正确且高效的代码
  2. 分析部分
    1. 词法分析,生成词素
    2. 语法分析,生成语法树
    3. 语义分析,收集类型信息,进行语义检查
    4. 中间代码生成

静态分析

  1. 静态分析:不执行程序,推导程序的行为,分析程序的性质
  2. 控制流:程序执行的流程
  3. 数据流:数据在控制流上的传递
  4. 通过分析控制流和数据流,可以知道更多关于程序的性质,据此优化代码

Go编译器优化

编译器思路优化

  1. 场景:面向后端长期执行任务
  2. 用编译器时间换取更高效的机器码

函数内联

内联:将被调用函数的函数体的副本替换到调用位置上,同时重写代码以反映参数的绑定

优点:

​ 消除函数调用开销

​ 将过程间分析转化为过程内分析,帮助其他优化