Go的高质量编码和性能优化 | 青训营笔记

83 阅读6分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 7 天

Go的高质量编码和性能调优

本文内容需要你有Go语言基础,本文主要介绍:

  • 如何编写更简洁清晰的代码
  • 常用Go语言2程序优化手段
  • 熟悉Go语言性能分析工具
  • 了解工程中性能优化的原则和流程

高质量编程

  • 高质量编程简介
  • 编码规范
  • 性能优化建议

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

高质量代码所要考虑到的情况:

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

编码原则

实际场景千变万化,各种语言的特效和语法各不相同,但是高质量编程遵循的原则是相同的。

  • 简单性:消除多余的复杂性,以简单清晰的逻辑编写代码,不理解的代码无法修复改进。
  • 可读性:代码是写给人看的,编写可维护的代码的第一步是确保代码的可读。在项目迭代中,代码可能会存在很长时间,可能会被很多人阅读,可读性是十分重要的。
  • 生产力:团队整体工作效率非常重要

代码注释

注释应该做的:

  • 注释应该解释代码的作用
  • 注释应该解释代码如何做的
  • 注释应该解释代码实现的原因
  • 注释应该解释代码什么情况下会出错

variable

  • 简洁胜于冗长
  • 缩略词全大写,但当其位于变量首部且不需要导出的时候用小写。
  • 变量距离其被使用的地方越远则需要携带越多的上下文信息。全局变量在名字中需要更多的上下文信息使得在不同地方可以轻易的辨认出其含义。

image.png

function

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

package

  • 只有小写字母组成,不包含大写字母和下划线
  • 简短并包含一定的上下文信息
  • 不要和标准库同名
  • 不使用常用变量作为包名
  • 使用单数不使用复数
  • 谨慎使用缩写,例如使用fmt不破坏上下文但是更简短

代码格式

推荐使用gofmt自动化格式化代码, gofmt是Go官方提供的工具,能自动格式化Go语言为官方统一风格,goimports也是go官方提供的工具,实际等于gofmt加上依赖包管理,自动增删依赖包引用,将依赖包按字母序列分类。

性能优化建议

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

性能表现需要实际数据衡量。go语言提供了支持基准性能测试的benchmark工具 go test -bench=. -benchmem

image.png 第一个是函数名,后面依次是执行次数,每次执行耗时,每次执行申请内存,每次执行申请几次内存。

slice预分配内存

在使用make初始化切片的时候提供容量信息。

image.png

  • 切片的本质是一个数组片段的描述,包括数组指针,片段的长度,片段的容量
  • 切片操作并不复制切片指向的元素
  • 创建一个新的切片会复用原来切片的底层数组 由于新的切片会复用之前的数组,原切片较大,代码在原切片基础上建立小切片,原底层数组在内存中有引用始终得不到释放。可以用copy替代re-slice,释放用不到的数组内容来减少对内存的使用。

map预分配内存

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

使用string.Builder

常见字符串拼接方式

func Puls(n int,str string){
    s:=""
    for i:=0;l<n;i++{
        s+=str
    }
    return s
}

func StringBuilder(n int,str string){
    var builder strings.Builder
    for i:=0;l<n;i++{
        builder.WriterString(str)
    }
    return builder.String()
}

这个原理和java的字符串拼接类似,字符串在go中是不可变的类型,字符串相加会转换成builder对象再进行相加再转换为string,中间的多次对象创建会损耗性能,使用builder底层是[]byte数组可以直接对字符串进行追加,减少了对象创建次数,因此性能更好。

对于strings.builder还有优化的空间和bytes.Buffer一样存在grow的方法进行空间的预分配。

使用空结构体节省内存

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

使用atomic包

在实际的多线程开发情况下,会出现并发问题,有多种解决办法,对于相加的操作可以使用atomicCounter包里的AddInt32方法。这里和java的原子操作类似,减少锁的使用来提高性能。

性能调优实战

性能调优原则:

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

在优化的过程中我们希望应用在什么地方耗费了多少cpu,memory,pprof是用于可视化和分析性能的分析数据工具。

image.png

解析工具的使用,首先引入依赖


import (
   "log"
   "net/http"
   _ "net/http/pprof" //自动开启pprof到http
   "runtime"
   "time"
)

func main() {
   runtime.GOMAXPROCS(1) //限制cpu数量
   runtime.SetMutexProfileFraction(1) //开启锁调度跟踪
   runtime.SetBlockProfileRate(1) // 开启阻塞调用跟踪

   go func() {
      if err:= http.ListenAndServe(":6060",nil);err!=nil{
         log.Fatalln(err)
      }
   }()
   
   for {
       time.sleep(time.Second)
   }
}

在浏览器输入localhost:6060/debug/pprof就可以进入查看页面。

image.png

但是这种方式还是不够直观 使用命令:

go tool pprof -http=:8888 "http://127.0.0.1:6060/debug/pprof/profile"

可使用视化图表进行数据的分析,在执行命令的时候可能出现dot命令无法执行,则需要安装graphviz,安装的时候选择配置到系统Path,然后重启IDE再次执行就可以了。graphvi下载地址

image.png