这是我参与「第五届青训营 」伴学笔记创作活动的第 4 天
本文为青训营课程笔记,配合原课程食用效果更佳哦~
课程笔记
优化
一、优化是什么?
- 提升软件系统处理能力,减少不必要的消耗,充分发掘计算机算力
二、为什么优化?
- 用户体验:带来用户体验的提升 — 让刷抖音更丝滑,让双十一购物不再卡顿
- 资源高效利用:降低成本,提高效率 — 很小的优化乘以海量机器会是显著的性能提升和成本节约
三、优化点在哪?
-
业务层
- 针对特定场景,具体问题,具体分析
- 容易获得较大性能收益
-
内存管理
-
GC垃圾回收优化
-
需要保证安全性和正确性的前提下才能优化
-
评价标注:安全性,吞吐率,暂停时间,内存开销
-
追踪垃圾回收
-
清理不可达对象,三种方式:
- 将存活对象复制到另外的内存空间 (Copying GC)
- 将死亡对象的内存标记为“可分配“ (Mark-sweep GC)
- 移动并整理存活对象 (Mark-compact GC)
-
分代假说(优化方式)
-
情况:很多对象在分配出来后很快就不再使用了
-
思路:对年轻和老年的对象,制定不同的 GC 策略,降低整体内存管理的开销
-
实现:不同年龄的对象处于 heap 的不同区域
- 年轻代使用copying collection
- 老年代使用mark-sweep collection
-
-
-
引用计数
-
清理引用数为0的对象
-
优点
- 内存管理的操作被平摊到程序执行过程中
- 内存管理不需要了解 runtime 的实现细节:C++ 智能指针 (smart pointer)
-
缺点
- 维护引用计数的开销较大:通过原子操作保证对引用计数操作的原子性和可见性
- 无法回收环形数据结构 —— weak reference
- 内存开销:每个对象都引入的额外内存空间存储引用数目
- 回收内存时依然可能引发暂停
-
-
-
-
内存分配优化
-
原生分配机制
- TCMalloc: thread caching
- 每个 p 包含一个 mcache 用于快速分配,用于为绑定于p 上的 g 分配对象
- mcache 管理一组 mspan
- 当 mcache 中的 mspan 分配完毕,向 mcentral 申请带有未分配块的 mspan
- 当 mspan 中没有分配的对象,mspan 会被缓存在mcentral 中,而不是立刻释放并归还给 OS
-
问题分析
- 对象分配是非常高频的操作:每秒分配 GB 级别的内存
- 小对象占比较高
- Go 内存分配比较耗时
-
-
优化方案:Balanced GC
-
使用三个指针base, end, top 维护 GAB:
-
GAB 对于 Go 内存管理来说是一个对象
• 本质:将多个小对象的分配合并成一次达对象的分配
• 新的问题:GAB 的对象分配方式会导致内存被延迟释放
• 方案:用 copying GC 移动 GAB 中存活的对象
-
-
-
编译器优化
-
过程内分析:仅在函数内部进行分析
-
过程间分析:考虑函数调用时参数传递和返回值的数据流和控制流
-
内联:将被调用函数的函数体 (callee) 的副本替换到调用位置 (caller) 上,同时重写代码以反映参数的绑定
优点:消除函数调用开销,帮助逃逸分析
缺点:函数体变大编,译生成的 Go 镜像变大
-
Beast Mode
- 针对问题:Go 函数内联受到的限制较多,内联策略非常保守
- 解决方案:调整函数内联的策略
- 效果:Go 镜像增加 ~10%
-
-
注意:
- 用数据说话,而非猜测优化效果
- 一定要在保证接口稳定的前提下改进
- 多写测试样例,保证稳定性和正确性
总结收获
通过样例与知识点结合的方式,学到了一些GC和编译器优化方式;
通过样例的讲解,更了解了具体的优化策略选择;
又是收获满满的一天