这是字节青训营第三课:高质量编程与性能调优实战的笔记和总结

概要

准备

  • 尝试使用 test 命令,编写并运行简单测试
  • 尝试使用 -bench参数,对函数进行性能测试
  • 推荐阅读Go代码Review建议、Uber的Go编码规范

高质量编程

简介

高质量编程简言之就是代码达到正确、可靠、简洁清晰的目标:

正确性:考虑各种边界条件,错误的调用正确处理

可靠性:异常或错误处理策略保障依赖的服务出现异常能够处理

简洁:逻辑简单,后续调整或新增功能能快速支持

清晰:代码易于阅读理解,重构或修改功能不易出问题

编程原则

实际应用场景千变万化,各语法特性和语法各不相同,但原则相通

  • 简单性:逻辑清晰简单,无多余复杂性,易于理解改进
  • 可读性:代码给人看而非机器,可维护性前提是可读性
  • 生产力:团队整体效率非常重要

编码规范

代码格式

用 gofmt和goimports格式化代码和包,保证代码与官方推荐格式一致

注释

Good code has lots of comments,bad code requires lots of comments.

  • 注释应该解释代码作用,适合注释公共符合,参考官方代码
  • 注释应该解释代码如何做的,适合注释方法,参考官方代码
  • 注释应该解释代码实现的原因,解释代码外部因素,参考官方代码
  • 注释应该解释代码什么情况会出错
  • 包中每个公共符合:变量、常量、函数及结构体都要注释,参考官方代码

场景一


如图,Open应解释作用,IsTableFull解释则无必要,因为已见名知意

场景二


第一个注释因逻辑较为复杂,需要注释,而第二个则完全没必要

场景三


如图shouldRedirect=false若脱离上下文后很难理解,需注释说明原因

场景四


注释应提醒潜在限制条件或无法处理情况,让使用者无需了解细节

场景五



包中每个公共符号始如变量、常量、函数及结构体终要注释,唯一例外是不要注释实现接口的方法

小结

  • 代码是最好的注释
  • 注释应提供代码未表达出的上下文信息,包括作用、实现、原因、出错情况等

命名规范

Good naming is like good joke,if you have to explain it,it’s not funny --Dave Cheney

核心是降低阅读和理解代码的成本,重点考虑设计简洁清晰名称并考虑上下文信息

变量

  • 简洁胜于冗长

    index和i仅作用于for,index并未增加对程序的理解

    deadline指截止时间,函数作用更明确
  • 缩略词全大写,如HTTP不要Http,但当其位于变量开头不导出时用全小写,如xmlHTTPRequest代替XMLHTTPRequest
  • 变量距离使用的地方越远,命名要越详细携带越多上下文信息

函数

  • 函数名尽量简洁
  • 函数名不携带包名上下文信息,因为包名和函数名总成对出现

如http包有Serve和ServeHTTP方法两个命名,应选择Serve命名,因为使用时会携带包名。类似C++命名空间,Java的包名

  • 名为foo的包某函数返回类型是Foo,可省略类型信息而不歧义
  • 名为foo的包某函数返回T(非Foo)可在函数名中加类型信息

  • 简短并包含一定上下文信息,但也要谨慎用缩写
  • 只由小写字母组成(不包含大写、下划线等字符
  • 不要和标准库同名冲突
  • 不用常用变量名做包名如bufio而非buf
  • 用单数而非复数,如encoding

控制流程

  • 避免嵌套,保持正常流程清晰
  • 尽量保持正常代码路径为最小缩进,参考官方代码

    嵌套使逻辑理复杂,调整后简单清晰,易于新增代码
  • 互斥条件表驱动

如有并列的if嵌套逻辑:

func CalculateByCmd(cmd string,a,b int)(int,error){if strings.EqualFold(cmd,"add"){return a+b,nil}if strings.EqualFold(cmd,"sub"){return a-b,nil}if strings.EqualFold(cmd,"mul"){return a*b,nil}return 0,errors.New("cmd not exist")
}

通过表驱动做出以下优化:

var mapCalculate = map[string]func(a,b int) int{"add": func(a, b int) int {return a+b},"sub": func(a, b int) int {return a-b},"mul": func(a, b int) int {return a*b},
}func CalculateByCmd(cmd string,a,b int)(int,error){if v,ok := mapCalculate[cmd];ok{return v(a,b),nil}return 0,errors.New("cmd not exist")
}

功能通过多个功能线性组合更简单,避免复杂嵌套分支,因为故障大多出现在复杂条件和循环语句,不易维护

错误处理

错误的Wrap和Unwrap

fmt.Errorf用%w将错误关联到错误链,使每层调用方补充自己上下文,生成error跟踪链,参考官方代码

error相关的函数

  • errors.New():创建匿名变量直接表示错误,参考官方代码

  • errors.Is():判断错误断言,不同==,它能判断错误链中是否包含它,参考官方代码

  • errors.As():从错误链中提取想要的错误,参考官方代码

panic和recover

  • panic:不推荐用panic,因为会向上传递到调用栈顶,若协程中所有被defer函数都不包含 recover 就会造成程序崩溃,启动阶段发生不可逆转错误时,可在 init 或 main 中用 panic,参考代码

  • recover只能在被defer的函数中使用,嵌套无法生效,只在当前 goroutine 生效,参考官方代码,若需要更多上下文信息,可recover后在log中记录当前调用栈,参考官方代码

性能优化

满足正确性、可靠性、健壮性、可读性等质量前提下,设法提高程序的效率,性能对比测试代码,可参考代码

benchmark测试

性能需实际数据衡量,Go内置性能评估工具

结果

slice

  • 参考博客

  • 切片本质是一个有指针、长度、容量属性的数组的描述,切片操作时不复制切片指向的元素,而复用切片底层数组,尽可能用make() 初始化时提供容量信息,特别是append时防止拷贝:

    • append后长度<=cap时,直接利用原底层数组剩余空间
    • append后长度>cap时,分配更大区域容纳新底层数组
  • 在已有切片的基础上切片,若只用很小一段,但底层数组在内存仍占用大量空间无法释放,推荐用 copy 替代 re-slice

map

  • 不断向map添加元素会触发扩容
  • 根据实际需求提前预估好需要的空间
  • 提前分配好空间可以减少内存拷贝和 Rehash

字符串

  • 常见的字符串拼接方式:strings.Builder、bytes.Buffer、+,效率递减
  • 字符串在Go中是不可变类型,占用内存大小固定,用+拼接字符串会生成新的字符串,会开辟两字符串大小之和的新的空间,另两个内存是以倍数申请,底层都是[]byte 数组,bytes.Buffer 转化字符串时重新申请一块空间,存放生成的字符串,而 strings.Builder直接将底层[]byte 转换成字符串类型返回
  • 字符串同样支持预分配,可进一步提高拼接性能

空结构体的使用

  • 空结构体不占内存仅作为占位符
  • 可以作为map实现简单set

atomic包

  • 锁通过OS实现,属于系统调用,atomic通过硬件实现更高效
  • sync.Mutex 应该用来保护一段逻辑,不仅仅用于保护一个变量
  • 非数值可用 atomic.Value,它能承载一个interface{}

总结

  • 避免常见的性能陷阱可保证大部分程序的性能

  • 普通应用代码,不要一味追求性能,应当在满足正确可靠、简洁清晰等质量前提下调优

性能调优实战

性能调优原则

  • 要依靠数据不是猜测
  • 要定位最大瓶颈而不是细枝末节
  • 不要过早优化
  • 不要过度优化

性能分析工具

性能调优的核心是性能瓶颈的分析,对于Go程序,最方便是 pprof 工具

  • pprof 功能说明
    • pprof 是用于可视化和分析性能分析数据的工具
    • 可以知道应用在什么地方耗费了多少 CPU、memory 等运行指标
  • pprof实践

    • 排查 CPU 问题

      • 命令行分析

        • go tool pprof “http://localhost:6060/debug/pprof/profile?seconds=10”
      • top 命令
      • list 命令
      • 熟悉 web 页面分析
      • 调用关系图,火焰图
      • go tool pprof -http=:8080 “http://localhost:6060/debug/pprof/cpu”
    • 排查堆内存问题

      • go tool pprof -http=:8080 “http://localhost:6060/debug/pprof/heap”
    • 排查协程问题

      • go tool pprof -http=:8080 “http://localhost:6060/debug/pprof/goroutine”
    • 排查锁问题

      • go tool pprof -http=:8080 “http://localhost:6060/debug/pprof/mutex”
    • 排查阻塞问题

      • go tool pprof -http=:8080 “http://localhost:6060/debug/pprof/block”

pprof 的采样过程和原理

CPU 采样



启动采样时,进程向OS注册一个定时器,OS会每10ms向进程发送一个SIGPROF信号,进程接收到信号后就对当前调用栈进行记录。同时进程启动一个写缓冲的goroutine,它每隔100ms从进程中读取已记录的堆栈信息,并写入到输出流。当采样停止时,进程向OS取消定时器,不再接收信号,写缓冲读取不到新的堆栈时,结束输出。

堆内存采样


堆内存采样在实现上依赖内存分配器的记录,一些其他的内存分配,如栈内存、一些更底层使cgo调分配的内存,不会被采样记录,采样率是默认每分配512KB内存采样一次,采样率可以调整,设为1则每次分配都会记录。与CPU和goroutine都不同的是,内存的采样是个持续的过程,它记录从程序运行起的所有分配或释放的内存大小和对象数量,并在采样时谝历这些结果进行汇总

协程和系统线程采样


Goroutie采样会记录所有用户发起,也就是入口不是runtime开头的goroutine,以及main所在goroutine的信息和创建这些goroutine的调用栈
它们都是会在STW后,漏历所有goroutine/线程的列表〔图中的m就是GMP模型中的m,在golang中和线程对应)并输出堆栈,最后STW继续运行。该采样是立刻触发的全量记录,可以比较两个时间点的差值来得到某一时间段的指标

阻塞操作和锁竞争采样


两指标在流程和原理上相似,不过指标的采样率含义不同:

  • 阻塞操作的采样率是个阈值,超过阈值时间的阻塞操作才会被记录,1为每次操作都会记录。炸弹程序的main里面设置rate=1

  • 锁竟争的采样率是个比例,运行时会通过随机数来记录固定比例的锁操作,1为每次操作都记录

实现也基本相同,在阻塞或锁操作发生时,会算出消耗的时间,连同调用栈一起主动上报给采样器,采样时,采样器会遍历已记录的信息,统计出具体操作次数、调用栈和总耗时。同样可以算两个时间点的差值算出段时间内的操作指标

性能调优案例

基本概念

  • 服务:能单独部署,承载一定功能的程序
  • 依赖:Service A 的功能实现依赖 Service B 的响应结果,称为 Service A 依赖 Service B
  • 调用链路:能支持一个接口请求的相关服务集合及其相互之间的依赖关系
  • 基础库:公共的工具包、中间件

业务优化

  • 流程

    • 建立服务性能评估手段
    • 分析性能数据,定位性能瓶颈
    • 重点优化项改造
    • 优化效果验证
  • 建立压测评估链路
    • 服务性能评估
    • 构造请求流量
    • 压测范围
    • 性能数据采集
  • 分析性能火焰图,定位性能瓶颈
    • pprof 火焰图
  • 重点优化项分析
    • 规范组件库使用
    • 高并发场景优化
    • 增加代码检查规则避免增量劣化出现
    • 优化正确性验证
  • 上线验证评估
    • 逐步放量,避免出现问题
  • 进一步优化,服务整体链路分析
    • 规范上游服务调用接口,明确场景需求
    • 分析业务流程,通过业务流程优化提升服务性能

基础库优化

适应范围更广,覆盖更多服务,包括:

  • AB 实验 SDK 的优化

    • 分析基础库核心逻辑和性能瓶颈
    • 完善改造方案,按需获取,序列化协议优化
    • 内部压测验证
    • 推广业务服务落地验证
  • Go 语言优化
    • 适应范围广通用性强,接入简单只需调整编译配置
    • 优化方式
      • 优化内存分配策略
      • 优化代码编译流程,生成更高效的程序
      • 内部压测验证
      • 推广业务服务落地验证

代码示例

以上语法和实战代码示例,都能再这里找到

课后

  • 了解下其他语言的编码规范,是否和 Go 语言编码规范有相通之处,注重理解哪些共同点

  • 编码规范或者性能优化建议大部分是通用的,有没有方式能够自动化对代码进行检测?

  • 从链接中选择感兴趣的包,看看官方代码是如何编写的

  • 使用 Go 进行并发编程时有哪些性能陷阱或者优化手段?

  • 真实线上环境中,遇到的性能问题各种各样,搜索下知名公司(如uber)的官方公众号或者博客,里面有哪些性能优化的案例?

  • Go 语言本身在持续更新迭代,每个版本在性能上有哪些重要的优化点?

参考资料

  • 熟悉 Go 语言基础后的必读内容

  • Dave Cheney 关于 Go 语言编程实践的演讲记录

  • 《编程的原则:改善代码质量的101个方法》,总结了很多编程原则,按照是什么 -> 为什么 -> 怎么做进行了说明

  • 如何编写整洁的 Go 代码

  • Go 官方博客,有关于 Go 的最新进展

  • Dave Cheney 关于 Go 语言编程高性能编程的介绍

  • Go 语言高性能编程,博主总结了 Go 编程的一些性能建议

  • Google 其他编程语言编码规范

字节青训营第三课之高质量编程与性能调优实战的笔记和总结相关推荐

  1. 字节青训营第十三课之深入浅出RPC框架的笔记与总结.md

    基本概念 本地函数调用 函数调用完整过程如图,藏实际上编译器经常优化,参数和返回值少时直接将其存在寄存器,不需操作栈,直接online不需call: 将a和b的值入栈 经函数指针找到calculate ...

  2. 三个小项目入门Go语言|字节青训营笔记

    前言 这是青训营的第一课,今天的课程比较快速的讲解了go语言的入门,并配合三个小的项目实践梳理所学知识点,这里详细回顾一下这三个项目,结合课后作业要求做一些代码补充,并附上自己的分析,青训期间的所有课 ...

  3. “金三银四”春招指南之“性能调优”:MySQL+Tomcat+JVM,看完还怕面试官的轰炸?

    春招指南之"性能调优":MySQL+Tomcat+JVM,还怕面试官的轰炸? 01 MySQL性能调优 1.1 MySQL性能调优问题有哪些?怎么学? 关于这个,给大家看一份学习大 ...

  4. Spark商业案例与性能调优实战100课》第16课:商业案例之NBA篮球运动员大数据分析系统架构和实现思路

    Spark商业案例与性能调优实战100课>第16课:商业案例之NBA篮球运动员大数据分析系统架构和实现思路 http://www.basketball-reference.com/leagues ...

  5. Spark商业案例与性能调优实战100课》第2课:商业案例之通过RDD实现分析大数据电影点评系统中电影流行度分析

    Spark商业案例与性能调优实战100课>第2课:商业案例之通过RDD实现分析大数据电影点评系统中电影流行度分析 package com.dt.spark.coresimport org.apa ...

  6. 《Spark商业案例与性能调优实战100课》第17课:商业案例之NBA篮球运动员大数据分析系统代码实战

    <<<Spark商业案例与性能调优实战100课>第17课:商业案例之NBA篮球运动员大数据分析系统代码实战

  7. 《Spark商业案例与性能调优实战100课》第18课:商业案例之NBA篮球运动员大数据分析代码实战之核心基础数据项编写

    <Spark商业案例与性能调优实战100课>第18课:商业案例之NBA篮球运动员大数据分析代码实战之核心基础数据项编写

  8. 《Spark商业案例与性能调优实战100课》第15课:商业案例之纯粹通过DataSet进行电商交互式分析系统中各种类型TopN分析实战详解

    <Spark商业案例与性能调优实战100课>第15课:商业案例之纯粹通过DataSet进行电商交互式分析系统中各种类型TopN分析实战详解

  9. 《Spark商业案例与性能调优实战100课》第25课:Spark Hash Shuffle源码解读与剖析

    <Spark商业案例与性能调优实战100课>第25课:Spark Hash Shuffle源码解读与剖析

最新文章

  1. 说出一些数据库优化方面的经验?
  2. matlab 区间预测,用神经网络进行预测的MATLAB算法实现?
  3. list和map用法java,java 集合list和map的用法
  4. mysql 并发避免锁表_MYSQL锁表的用法,防止并发情况下的重复数据
  5. 博文视点大讲堂28期 “助你赢在软件外包行业”成功举办
  6. “朝阳群众”APP上线!解读怎么做一款深入群众的应用
  7. 关于数组相关乱七八糟
  8. linux 设备 总线 驱动 模型
  9. 模式识别(五)聚类的几种算法
  10. 关于按钮需要点两次才有用的问题
  11. Python实现毫秒级抢单,6翻了!
  12. 日本的美景,从谷歌卫星地图上就可以一撇究竟
  13. 阿里双十一 11 年:购物狂欢背后的技术演进
  14. 魔性!Python生成全网爆火的“蚂蚁呀嘿”
  15. java实现上传文件夹
  16. 2018年EI收录中文期刊目录【转】
  17. linux shell获取当前脚本所在目录
  18. 如何利用今日头条推荐规则, 打造阅读量100万+的爆款文章?
  19. 路由宽带运营商服务器未响应,路由器拨号失败服务器无响应的解决方法
  20. 将png图片转化为icon

热门文章

  1. 京东物流轨迹java_京东区块链 JD chain java demo实现
  2. 软件测试之补丁包测试
  3. Oracle两者同时满足,【判断题】逻辑或(OR)当多个条件同时满足时结果为真。
  4. Audio的framecount、framesize、sampleRate计算关系
  5. PHP编辑器哪个好用些?
  6. Java创建泛型数组的一种方法
  7. python打地鼠游戏代码_Python 0基础开发游戏:打地鼠(详细教程)VS code版本
  8. 论文排版图片一栏设置与文献引用的超链接设置-论文投稿经验总结-第3期
  9. el表达式截取字符串
  10. 明星的阶梯:威尼斯电影节 | 经济学人早报精选20210831