目录描述

  • Go语言上手-高质量编程与性能调优实战
  • 1.高质量编程
    • 1.简介
    • 2.编码规范
      • 2.1 代码格式
      • 2.2 注释
      • 2.3 命名规范
    • 3.性能优化建议
      • 3.1 Benchmark
      • 3.2 Slice
      • 3.3 Map
      • 3.4 字符串处理
      • 3.5 空结构体
      • 3.6 atomic包
  • 2.性能调优实战
    • 2.1 简介
    • 2.2 性能分析工具pprof
      • 2.2.1 功能简介
      • 2.2.2 排查实战
      • 2.2.3 采样过程和原理
    • 2.3 性能调优案例
      • 2.3.1 业务服务优化
      • 2.3.2 基础库优化
      • 2.3.3 Go语言优化

Go语言上手-高质量编程与性能调优实战

1.高质量编程

1.简介

高质量:编写的代码能够达到正确可靠、简洁清晰的目标可称之为高质量代码

  • 各种边界条件是否考虑完备
  • 异常情况处理,稳定性保证
  • 易读易维护

高质量编程遵循的原则:

  1. 简单性
    消除“多余的复杂性”,以简单清晰的逻辑编写代码
    不理解的代码无法修复改进
  2. 可读性
    代码是写给人看的,而不是机器
    编写可维护代码的第一步是确保代码可读
  3. 生产力
    团队整体工作效率非常重要

2.编码规范

  • 代码格式
  • 注释
  • 命名规范
  • 控制流程
  • 错误和异常处理

2.1 代码格式

  • gofmt
    Go语言官方提供的工具,能自动格式化Go语言代码为官方统一风格常见IDE都支持方便的配置
  • goimports
    也是Go语言官方提供的工具,实际等于gofmt加上依赖包管理,自动增删依赖的包引用、将依赖包按字母序排序并分类

2.2 注释

  • 解释代码的作用
    适合注释公共符号
  • 解释代码如何做的
    适合注释实现过程
  • 解释代码实现的原因
    适合解释代码的外部因素
    提供额外上下文
  • 解释代码什么情况会出错
    适合解释代码的限制条件
  • 公共符号始终要注释
    包中声明的每个公共的符号:变量、常量、函数以及结构都需要添加注释
    任何既不明显也不简短的公共功能必须予以注释
    无论长度或复杂程度如何,对库中的任何函数都必须进行注释
    不需要注释实现接口的方法

2.3 命名规范

变量

  1. 简洁胜于冗长
  2. 缩略词全大写,但当其位于变量开头且不需要导出时,使用全小写
    例如:使用ServeHTTP而不是ServeHttp
    使用XMLHTTPRequest或者xmlHTTPRequest
  3. 变量距离其被使用的地方越远,则需要携带越多的上下文信息
    全局变量在其名字中需要更多的上下文信息,使得在不同地方可以轻易辨认出其含义

函数

  1. 函数名不携带包名的上下文信息,因为包名和函数名总是成对出现的
  2. 函数名尽量简短
  3. 当名为foo的包某个函数返回类型Foo时,可以省略类型信息而不导致歧义
  4. 当名为foo的包某个函数返回类型T时(T并不是Foo),可以在函数名中加入类型信息

package

  1. 只由小写字母组成,不包含大写字母和下划线等字符
  2. 简短并包含一定的上下文信息,例如:schema、task等
  3. 不要与标准库同名,例如不要使用sync或者strings

以下规则尽量满足,以标准库包名为例:

  1. 不使用常用变量名作为包名,例如使用bufio而不是buf
  2. 使用单数而不是复数,例如使用encoding而不是encodings
  3. 谨慎地使用缩写,例如使用fmt在不破坏上下文的情况下比format更加简短

控制流程

  1. 避免嵌套,保持正常流程清晰
  2. 尽量保持正常代码路径为最小缩进
    优先处理错误情况/特殊情况,尽早返回或继续循环来减少嵌套
    最常见的正常流程的路径被嵌套在两个if条件内
    成功的退出条件是return nil,必须仔细匹配大括号来发现
    函数最后一行返回一个错误,需要追溯到匹配的左括号,才能了解何时会触发错误
    如果后续正常流程需要增加一步操作,调用新的函数,则又会增加一层嵌套

错误和异常处理

  1. 简单错误
    简单的错误指的是仅出现一次的错误,且在其他地方不需要捕获该错误
    优先使用errors.New来创建匿名变量来直接表示简单错误
    如果有格式化的需求,使用fmt.Errorf
  2. 错误的Wrap和Unwrap
    错误的Wrap实际上是提供了一个error嵌套另一个error的能力,从而生成一个error的跟踪链
    在fmt.Errorf中使用:%w关键字来将一个错误关联至错误链中
  3. 错误判定
    判定一个错误是否为特定错误,使用errors.ls
    不同于使用==,使用该方法可以判定错误链上的所有错误是否含有特定的错误

    在错误链上获取特定种类的错误,使用errors.As
  4. panic
    不建议在业务代码中使用panic
    调用函数不包含recover会造成程序崩溃
    若问题可以被屏蔽或解决,建议使用error代替panic
    当程序启动阶段发生不可逆转的错误时,可以在init或者main函数中使用panic
  5. recover
    recover只能在被defer的函数中使用
    嵌套无法生效
    只在当前goroutine生效
    defer的语句是后进先出

    如果需要更多的上下文信息,可以recover后在log中记录当前的调用栈

总结:

  1. error尽可能提供简明的上下文信息链,方便定位问题
  2. panic用于真正异常的情况
  3. recover生效范围,在当前goroutine的被defer的函数中生效

3.性能优化建议

  • 性能优化的前提是满足正确可靠、简洁清晰等质量因素
  • 性能优化是综合评估,有时候时间效率和空间效率可能对立
  • 针对Go语言特性,介绍Go相关的性能优化建议

3.1 Benchmark

  • 性能表现需要实际数据衡量
  • Go语言提供了支持基准性能测试的Benchmark工具



3.2 Slice

预分配内存

  • 尽可能在使用make()初始化切片时提供容量信息
  • 切片本质是一个数组片段的描述
    包括数组指针
    片段的长度
    片段的容量(不改变内存分配情况下的最大长度)
  • 切片操作并不复制切片指向的元素
  • 创建一个新的切片会复用原来切片的底层数组

大内存未释放

  • 在已有切片基础上创建切片,不会创建新的底层数组
  • 场景
    原切片较大,代码在原切片基础上新建小切片
    原底层数组在内存中有引用,得不到释放
  • 可使用copy替代re-slice



3.3 Map

预分配内存

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

3.4 字符串处理

使用strings.Builder

  • 常见的字符串拼接方式


三种拼接方式的效果:


总结:使用+拼接性能最差,strings.Builder,bytes.Buffer相近,bytes.Buffer更快
分析:

  1. 字符串在Go语言中是不可变类型,占用内存大小是固定的
  2. 使用+每次都会重新分配内存
  3. strings.Builder,bytes.Buffer底层都是[]byte数组
  4. 内存扩容策略,不需要每次拼接重新分配内存
  • 对strings.Builder 和 strings.Buffer分析
    bytes.Buffer转化为字符串时重新申请了一块空间
    strings.Builder直接将底层的[]byte转换成了字符串类型返回

  • 进一步优化:进行预分配

3.5 空结构体

使用空结构体节省内存

  • 空结构体 struct{}实例不占据任何的内存空间
  • 可作为各种场景下的占位符使用
    节省资源
    空结构体本身具备很强的语义,即这里不需要任何值,仅作为占位符



总结:实现Set,可以考虑用map来代替,对于这个场景,只需要用到map的键,而不需要值,即使是将map的值设置为bool类型,也会多占据1个字节空间
一个开源实现:https://github.com/deckarep/golang-set/blob/main/threadunsafe.go

3.6 atomic包

  • 锁的实现是通过操作系统来实现,属于系统调用
  • atomic操作时通过硬件实现,效率比锁高
  • sync.Mutex应该用来保护一段逻辑,不仅仅用于保护一个变量
  • 对于非数值操作,可以使用atomic.Value,能承载一个interface{}

2.性能调优实战

2.1 简介

性能调优原则

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

2.2 性能分析工具pprof

说明

  • 希望知道应用在什么地方消耗了多少CPU,Memory
  • pprof是用于可视化和分析性能分析数据的工具

2.2.1 功能简介

2.2.2 排查实战

准备工作:

  • GitHub(Wolfogre)
  • https://github.com/wolfogre/go-pprof-practice
  • 项目提前埋入了一些炸弹代码,产生可观测的性能问题
  • golang pprof 实战:https://blog.wolfogre.com/posts/go-ppof-practice/,如下图
package mainimport (// 略_ "net/http/pprof" // 会自动注册 handler 到 http server,方便通过 http 接口获取程序运行采样报告// 略
)func main() {// 略runtime.GOMAXPROCS(1) // 限制 CPU 使用数,避免过载runtime.SetMutexProfileFraction(1) // 开启对锁调用的跟踪runtime.SetBlockProfileRate(1) // 开启对阻塞操作的跟踪go func() {// 启动一个 http server,注意 pprof 相关的 handler 已经自动注册过了if err := http.ListenAndServe(":6060", nil); err != nil {log.Fatal(err)}os.Exit(0)}()// 略
}

引入net/http/pprof 这个包,它会将pprof的入口注册到/debug/pprof这个路径下,我们可以通过浏览器打开这个路径,来查看一些基本的性能统计,运行【炸弹】,并等待它运行稳定

具体操作步骤:

  1. 将程序在https://github.com/wolfogre/go-pprof-practice中下载
  2. 在Goland中打开后,打开Terminal
  3. 输入:go mod init example.com/m
  4. 输入:go mod init

最后生成:


运行main.go 看电脑能不能抗住一分钟【看资源是否吃紧】,控制台会不停打印日志:


浏览器查看指标:

保持程序运行,打开浏览器访问:http://localhost:6060/debug/pprof/

页面上展示了可用的程序运行采样数据:

因为 cmdline 没有什么实验价值,trace 与本文主题关系不大,threadcreate 涉及的情况偏复杂,所以这三个类型的采样信息这里暂且不提。
除此之外,其他所有类型的采样信息本文都会涉及到,且炸弹程序已经为每一种类型的采样信息埋藏了一个对应的性能问题

排查 CPU 占用过高【profile】
①运行程序,在控制台输入:go tool pprof http://localhost:6060/debug/pprof/profile
等待一会儿后,进入一个交互式终端:


②输入 top 命令,查看 CPU 占用较高的调用:

参数 概念
flat 当前函数本身的执行耗时
flat% flat占CPU总时间的比例
sum% 上面每一行的flat%总和
cum 指当前函数本身加上其调用函数的总耗时
cum% cum占CPU总时间的比例
  • 什么情况下Flat == Cum
    函数中没有调用其他函数
  • 什么情况下Flat == 0
    函数中只有其他函数的调用
    很明显,CPU 占用过高是 github.com/wolfogre/go-pprof-practice/animal/felidae/tiger.(*Tiger).Eat 造成的。

③输入 list Eat,查看问题具体在代码的哪一个位置:



可以看到,是第 24 行那个一百亿次空循环占用了大量 CPU 时间,至此,问题定位成功!

④图形化显示调用栈信息
先下载graphviz:http://www.graphviz.org/download/
安装教程:
https://blog.csdn.net/qq_52661119/article/details/121676373
https://blog.csdn.net/wyf2017/article/details/107792316

在交互式终端里输入 web【虽然这个命令的名字叫“web”,但它的实际行为是产生一个 .svg 文件,并调用你的系统里设置的默认打开 .svg 的程序打开它。如果你的系统里打开 .svg 的默认程序并不是浏览器(比如可能是你的代码编辑器),这时候你需要设置一下默认使用浏览器打开 .svg 文件】


为了方便进行后面的实验,注释掉相关代码:

func (t *Tiger) Eat() {log.Println(t.Name(), "eat")//loop := 10000000000//for i := 0; i < loop; i++ {// // do nothing//}
}

排查内存占用过高【heep】
运行程序,在控制台输入:go tool pprof -http=:8080 http://localhost:6060/debug/pprof/heap

运行结果:

在view里面有六种展示方式:

在source情况下:

在sampel情况下有四种情况:

  • alloc_objects:程序累计申请的对象数
  • alloc_space:程序累计申请的内存大小
  • inuse_objects:程序当前持有的对象数
  • inuse_space:程序当前占用的内存大小

在堆内存采样中,默认展示的是inuse_space视图,只展示当前持有的内存,但如果有的内存已经释放,这时inuse采样就不会展示,就需要切换到alloc_space指标

将相关代码注释:

func (m *Mouse) Steal() {log.Println(m.Name(), "steal")max := constant.Gifor len(m.buffer) * constant.Mi < max {//m.buffer = append(m.buffer, [constant.Mi]byte{})}
}
func (d *Dog) Run() {log.Println(d.Name(), "run")//_ = make([]byte, 16 * constant.Mi)
}

排查协程泄露【goroutine】
运行程序,在控制台输入:go tool pprof -http=:8080 http://localhost:6060/debug/pprof/goroutine

运行结果:


由于这个图不直观,所以切换为flamegraph【火焰图】:


火焰图的特点:

  • 由上到下表示调用顺序
  • 每一块代表一个函数,越长代表占用CPU的时间更长
  • 火焰图是动态的,支持点击块进行分析

将视图转到source情况下:


注释相应的代码:

func (w *Wolf) Drink() {log.Println(w.Name(), "drink")for i := 0; i < 10; i++ {go func() {//time.Sleep(30 * time.Second)}()}
}

排查锁的争用【mutex】
运行程序,在控制台输入:go tool pprof -http=:8080 http://localhost:6060/debug/pprof/mutex

运行结果:

将视图转到source情况下:


注释相应的代码:

func (w *Wolf) Howl() {log.Println(w.Name(), "howl")m := &sync.Mutex{}m.Lock()go func() {time.Sleep(time.Second)//m.Unlock()}()m.Lock()
}

排查阻塞操作【block】
运行程序,在控制台输入:go tool pprof -http=:8080 http://localhost:6060/debug/pprof/block


运行结果:


将视图转到source情况下:


注释相应的代码:

func (c *Cat) Pee() {log.Println(c.Name(), "pee")//<-time.After(time.Second)
}

2.2.3 采样过程和原理

CPU

  • 采样对象:函数调用和它们占用的时间

  • 采样率:100词/秒,固定值

  • 采样时间:从手动启动到手动结束

    具体流程:

  • 操作系统:每10ms向进程发送一次SIGPROF信号

  • 进程:每次接收到SIGPROF会记录调用堆栈

  • 写缓冲:每100ms读取已经记录的调用栈并写入输出流



Heap-堆内存

  • 采样程序通过内存分配器在堆上分配和释放的内存,记录分配/释放的大小和数量
  • 采样率:每分配512KB记录一次,可在运行开头修改,1为每次分配均记录
  • 采样时间:从程序运行开始到采样时
  • 采样指标:alloc_space,alloc_objects,inuse_space,inuse_objects
  • 计算方式:inuse = alloc-free

Goroutine-协程 & ThreadCreate-线程创建

  • Goroutine
    记录所有用户发起且在运行中的goroutine(即入口非runtime开头的)
    runtime.main的调用栈信息
  • ThreadCreate
    记录程序创建的所有系统线程的信息

Block-阻塞 & Mutex-锁

2.3 性能调优案例

介绍实际业务服务性能优化的案例,对逻辑相对复杂的程序如何进行性能调优

  • 业务服务优化
  • 基础库优化
  • Go语言优化

2.3.1 业务服务优化

基本概念:

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

系统部署的简单示意图:

流程:

  • 建立服务性能评估手段
    服务性能评估方式:1.到哪都benchmark无法满足复杂逻辑分析 2.不同负载情况下性能表现差异
    请求流量构造:1.不同请求参数覆盖逻辑不同 2.线上真实流量情况
    压测范围: 1. 单机器压测 2.集群压测
    性能数据采集:1.单机性能数据 2.集群性能数据
  • 分析性能数据,定位性能瓶颈
    使用库不规范
    高并发场景优化不足
  • 重点优化项改造
    正确性是基础
    响应数据diff:1.线上请求数据录制回放 2. 新旧逻辑接口数据diff
  • 优化效果验证
    重复压测验证
    上线评估优化效果: 1.关注服务检测 2.逐步放量 3.收集性能数据
  • 进一步优化,服务整体链路分析
    规范上游服务调用接口,明确场景需求
    分析链路,通过业务流程优化提升服务性能

2.3.2 基础库优化

AB实验SDK的优化

  • 分析基础库核心逻辑和性能瓶颈
    设计完善改造方案
    数据按需获取
    数据序列化协议优化
  • 内部压测验证
  • 推广业务服务落地验证

2.3.3 Go语言优化

编译器&运行时优化

  • 优化内存分配策略
  • 优化代码编译流程,生成更高效的程序
  • 内部压测验证
  • 推广业务服务落地验证

优点:

  • 接入简单,只需要调整编译配置
  • 通用性强

1.3 Go语言上手-高质量编程与性能调优实战相关推荐

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

    这是字节青训营第三课:高质量编程与性能调优实战的笔记和总结 概要 准备 尝试使用 test 命令,编写并运行简单测试 尝试使用 -bench参数,对函数进行性能测试 推荐阅读Go代码Review建议. ...

  2. golang(3)高质量编程与性能分析

    1. 编程原则 实际场景千变万化,各种语言各不相同,但是高质量编程遵循的原则是相通的. 简单性 消除"多余的复杂性",以简单清晰的逻辑写代码 不理解的代码无法修复改进 可读性 代码 ...

  3. 《Java性能调优实战》笔记(一)Java编程性能调优、多线程性能优化

    文章目录 一.Java性能调优概述 1.1 性能调优标准 1.2 制定性能调优策略 二.Java编程性能调优 2.1 字符串 2.2 正则表达式 2.3 ArrayList和LinkedList的选择 ...

  4. mysql性能调优与架构设计_了解架构设计远远不够!一文拆解 Tomcat 高并发原理与性能调优

    来源 | 码哥字节 上帝视角拆解 Tomcat 架构设计,在了解整个组件设计思路之后.我们需要下凡深入了解每个组件的细节实现.从远到近,架构给人以宏观思维,细节展现饱满的美.关注「码哥字节」获取更多硬 ...

  5. 了解架构设计远远不够!一文拆解 Tomcat 高并发原理与性能调优

    来源 | 码哥字节 上帝视角拆解 Tomcat 架构设计,在了解整个组件设计思路之后.我们需要下凡深入了解每个组件的细节实现.从远到近,架构给人以宏观思维,细节展现饱满的美.关注「码哥字节」获取更多硬 ...

  6. Day783.网络通信优化之I/O模型:如何解决高并发下I/O瓶颈 -Java 性能调优实战

    网络通信优化之I/O模型:如何解决高并发下I/O瓶颈 Hi,我是阿昌,今天学习记录的是关于网络通信优化之I/O模型:如何解决高并发下I/O瓶颈. 提到 Java I/O,相信你一定不陌生. 可能使用 ...

  7. Go 语言高质量编程

    编写高质量的 Go 代码~ 前言: 本次课程简要介绍了高质量编程的定义和原则,分享了代码格式.注释.命名规范.控制流程.错误和异常处理五方面的常见编码规范,帮助我们在今后的开发过程中写出更加优秀的代码 ...

  8. 【高质量编程指南笔记】

    高质量编程指南-笔记 一.文件结构 1.1 版权和版本的声明 1.2 头文件的结构 1.3 定义(.cpp) 文件的结构 1.4 头文件的作用 1.5 目录结构 二.程序的版式 2.1 长行拆分 2. ...

  9. r语言 tunerf函数_R语言 | 一网打尽高质量统计分析与机器学习包

    原标题:R语言 | 一网打尽高质量统计分析与机器学习包 146+72本期刊<SCI期刊分析+选刊网站>免费领 解螺旋公众号·陪伴你科研的第2232天 常用统计方法包+机器学习包(名称.简介 ...

  10. 立足自主创新,编程猫助推国内高质量编程教育普及

    来源:金融界百家 2017年,国务院印发<新一代人工智能发展规划>,要求在中小学阶段设置人工智能相关课程,逐步推广编程教育.编程教育逐渐受到广泛重视,作为国内少儿编程赛道先行者,编程猫始终 ...

最新文章

  1. 网卡绑定技术linux c,Linux多网卡绑定
  2. 华为S5300系列交换机V100R005SPH008热补丁
  3. 【福利】微信小程序130个精选Demo合集
  4. 形容PHP程序员的语句,形容程序员的句子
  5. sqlrelay mysql_SQLrelay数据库负载均衡
  6. aws rds监控慢sql_AWS RDS SQL Server入门
  7. Linux统计文件夹下文件数量
  8. 【优化电价】基于matlab遗传算法求解共享汽车电价优化问题【含Matlab源码 1162期】
  9. 增益带宽积(GBWP、GBW、GBP、GB)
  10. 蓝桥杯试题java_java蓝桥杯试题
  11. ORBSLAM3整体框架
  12. mysql怎么在查询_Mysql查询操作
  13. 奇迹之剑萌新晋升大神辅助攻略 奇迹之剑游戏脚本挂机工具介绍
  14. 时域反射仪(TDR)介绍
  15. 基于jsp+mysql+Spring+SpringMVC+mybatis的高速公路收费管理系统
  16. 电脑没有使用计算机进入睡眠状态,电脑打不开,屏上显示:无视频输入,进入睡眠模式。怎么处理...
  17. Layui数据表格监听单元格编辑恢复原值
  18. freertos和ucos的区别
  19. iNotePad流氓软件卸载
  20. 如何在工作中提升自己的学习能力

热门文章

  1. [!] The `Paopao [Debug]` target overrides the `PODS_ROOT` build setting defined in `Pods/Target Supp
  2. PhantomReference虚引用
  3. iCollections—桌面管理工具
  4. 外网访问家庭局域网方案,解决运营商100.x.x.x内网IP问题
  5. html中js alert函数,javascript的alert是什么
  6. De-Sim示例分析(三)SIR传染病模型
  7. 柳神PAT甲级真题目录
  8. 【区块链108将】千方基金点付大头:投资区块链,不要让过往认知限制你的想象
  9. 骆昊python100天百度云_GitHub - Luffy-cc/Python-100-Days: Python - 100天从新手到大师
  10. antd 表单通过form.getFieldsValue获取不全tabs组件下表单数据的问题