GO语言性能优化实践课程笔记| 青训营笔记

88 阅读5分钟

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

内存管理

1.1 自动内存管理

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

自动内存管理(垃圾回收):由程序语言的运行时系统管理动态内存
好处:1.避免手动内存管理,使程序员可以专注于实现业务逻辑 保证内存使用的正确性和安全性
double-free problem 连续释放两次内存 user-after-free problem 释放内存后再去使用
三个任务 为新对象分配空间
找到存活对象
回收死亡对象的内存空间
mutator:业务线程,分配新对象,修改对象指向关系
Collector: GC线程,找到存活对象,回收死亡对象的内存空间 GC算法分类: Serial GC:只有一个collector,会暂停

image.png

Parallel GC:支持多个collectors同时回收的GC算法,会暂停

image.png

Concurrent GC:mutators和collectors可以同时执行,不需要暂停

image.png

Collectors必须感知对象指向关系的改变,当一个已标记对象指向了一个新的对象,这个新的对象也要被标记

image.png

评估一个GC算法的好坏:

  1. 安全性
    2.吞吐率: 1- GC时间/程序执行总时间
    3.暂停时间 业务能否感知到暂停 4.内存开销,GC元数据开销

1.2追踪垃圾回收

对象被回收的条件:指针指向关系不可达的对象 步骤:1.标记根对象 静态变量、全局变量、常量、线程栈等
2.根据根对象找到所有的可达对象 求指针指向关系的传递闭包
3.清理所有不可达对象:
三种不同的标记和清理策略

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

image.png
2. Mark-sweep GC 将死亡对象的内存标记为可分配(free)

image.png
3.Compact GC移动并整理存活对象,与Copying GC类似,不过Compact GC是原地整理对象

image.png

1.3分带GC

分代假说: 很多对象在分配出来后很快就不再使用了 年轻代:经历过GC的次数较少的对象,可以采用copying collection 老年代: 对象趋向于一致或者,反复复制开销较大,可以采用mark-sweep collection原地整理

1.4引用计数

每个对象都有一个与之关联的引用数目
对象存活的条件:当且仅当引用数大于0
优点:内存管理的操作被平摊到程序执行过程中,程序执行的过程中内存管理的操作同时被执行。
内存管理不需要了解runtime的实现细节,只要维护对象的引用技术即可实现内存管理
缺点:维护引用技术的开销较大:通过原子操作保证对引用计数操作的原子性和可见性
无法回收环形数据结构
内存开销较大,每个对象都引入额外内存空间存储引用数目
回收内存时仍然可能引发暂停

GO内存分配

分块

1.调用系统调用mmap()向OS申请一大块内存,例如4MB
2.将一大块内存划分成若干个块,例如8KB,一个小块称为mspan
mspan分为两类:1.noscan mspan:分配不包含指针的对象-GC不需要扫描
2.scan mspan:分配包含指针的对象 -GC需要扫描

image.png 3.再将8KB的块划分成特定大小的小块,用于对象分配

同步调用和异步调用

同步调用:调用方在调用过程中,持续等待返回结果。
异步调用:调用方在调用过程中,不直接等待返回结果, 而是执行其他任务,结果返回形式通常为回调函数。

image.png

GMP模型

G:gorotine(协程)
M:machine(内核线程)
P:processor(调度器)
go 1.1版本之前时候过使用的是GM模型+全局队列的模式。

image.png 新建一个协程G的时候会放入全局队列中,每次执行一个协程G的时候,内核线程M会从全局队列中获取一个协程G执行,因为内核线程M存在多个所以存在并发问题,因此每次从队列中取协程G的时候都要加锁,所以当高并发的时候就会存在性能问题。

解决办法:给内核线程分配一个协程队列 GMP模型 + 全局队列

M:N:=内核线程 :协程

p的个数对应的是设置的内核个数

image.png 新建一个协程G会优先放到本队队列P中,如果本地队列P满了,则会把本地队列的一半转移到全局队列中,本地队列为空的时候,就会从全局队列中去取。如果全局队列为空的话就会重其他本地队列拿一半协程G放到自己本地队列P中。如果中途协程阻塞了,本队队列P会在其他的内核线程上运行。

内存分配-缓存

image.png

每个P包含一个mcache用于快速分配,用于为绑定于p上的g分配对象。
mcache管理一组mspan
当mcache中的mspan分配完毕,向mcentral申请带有未分配快的mspan
当mspan中的没有分配的对象,mspan会被缓存在mentral中,而不是立刻释放并归还给OS
分配路径长: g->m->p->mcache->mspan->memory block->return pointer
pprof:对象分配的函数时最频繁调用的函数之一

字节跳动的优化方案:BanlancedGC

每个goroutine绑定一大块内存(1kb),称作goroutine allocaltion buffer(GAB)

  • GAB用于noscan类型(不包含指针的对象)分配且对象<128B

  • 使用三个指针维护GAB:base,end,top

  • 指针碰撞风格对象分配,无须和其他分配请求互斥,分配动作简单高效

  • 本质:将多个小对象的分配合并成一次大对象的分配

  • 问题:GAB的对象分配方式会导致内存被延迟释放,大对象里只要至少存在一个小对象,则不能得到释放。

  • 解决方式:类似copiyingGC的算法管理小对象,当GAB总大小超过一定阈值时,将GAB中存活的对象复制到另外分配的GAB中 参考链接:www.jianshu.com/p/94a560931…