高质量编程简介以及性能调优 | 青训营笔记

99 阅读6分钟

高质量编程简介以及性能调优 | 青训营笔记

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

内容目录

  • 高质量编程
  • 性能调优

高质量

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

什么是高质量?

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

Go语言编程原则

  • 简单性:消除“多余的复杂性”,以简单清晰的逻辑编写代码
  • 可读性:代码是写给人看的,而不是机器;编写可维护代码的第一步是确保代码可读
  • 生产力:团队整体工作效率非常重要

代码规范

  • 代码格式:
    • gofmt,go语言官方提供的工具,能够自动格式化Go语言的代码为官方的统一风格
    • goimports:也是go语言官方提供的工具,实际等于gofmt加上依赖包管理
  • 注释:
    • 公共符号(函数,结构体等)始终需要注释
    • 注释应该解释代码的作用
    • 注释应该解释代码是如何做的:解释简单的逻辑,在函数体当中,需要解释的地方。
    • 注释应该解释代码实现的原因
    • 注释应该解释代码什么情况会出错:比如对变量传入的一些限制等
  • 命名规范:
    • 变量名称
      • 简洁胜于冗长
      • 缩略词全部大写,当其期位于变量开头且不需要导出的时候,使用全小写:go语言当中导出属性需要是首字母大写的,否则外部无法访问,同时结构体如果需要被json或者bson识别,也需要将其中的变量名首字母大写,否则是无法进行填入的,因为这些变量都不是公有的
        • 例如使用ServeHTTP 而不是 ServeHttp
    • 函数名
      • 变量距离其被使用的地方越远,则需要携带越多的上下文信息
      • 函数名不携带包名的上下文信息,因为包名和函数名总是成对出现。
      • 函数名尽量简短。
    • 包名
      • 不使用常用变量名作为包名,例如使用bufio而不是buf
      • 使用单数而不是复数
      • 谨慎的使用缩写
    • 小结:
      • 核心目标是降低阅读理解代码的成本
      • 重点考虑上下文信息,设计简洁清晰的名称 image.png image.png
  • 控制流程
    • 尽量保持正常代码路径为最小缩进
    • 减少嵌套分支。
  • 错误和异常处理
    • 简单错误
      • 简单的错误指的是仅出现一次的错误,且在其他地方不需要捕获该错误
      • 优先使用errors.New 来创建匿名变量来直接表示简单错误。
      • 如果格式化的需求,使用fmt.Errorf
    • 错误的Wrap和Unwrap
    • 错误判定
    • recover
    • panic image.png image.png image.png image.png image.png image.png

练习

看下面一段代码

package main

import "fmt"

func main() {
   if true {
      defer fmt.Println("1")
   } else {
      defer fmt.Println("2")
   }
   defer fmt.Println("3")
}

运行结果是什么

image.png

这里可以发现其实defer类似于栈的结构,先defer的会后执行,后defer的先执行

性能优化

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

Benchmark 基准性能测试工具

image.png

image.png

slice优化

slice的底层数据结构

type slice struct{
    array unsafe.Pointer
    len int
    cap int
}

image.png

在扩容的时候会增加时间消耗,所以我们在使用slice的时候可以采用预分配内存的机制,然后得到结果。

还有一个问题就是大内存未释放,在已有的切片基础上创建切片,不会创建新的底层数组,这里其实可以认为,我们本质上不过是调整了一个指针位置指向我们想要的部分,如果我们进行操作,还是在原来的切片上进行的操作。

  • 原切片比较大,代码在原切片的基础上创建小切片
  • 原底层数组在内存中有引用,得不到释放(不可被垃圾回收机制识别)

image.png

就像上面这样的逻辑,由于origin[len(origin)-2 : ],不会开辟新的空间,不过是将指针调整到了origin[len(origin)-2]的位置,所以利用下面这种copy可以很好的解决问题。

map优化

map的底层数据结构 也需要预分配内存

字符串处理

image.png

  • 采用左边的方式的时候会开辟一个新内存空间
  • 采用右侧的方式维护了一个扩容的策略,底层都是[]byte数组。

image.png

空结构体

image.png

image.png

用于实现一个set,主要是进行查重,利用map的键值是不能重复的这个特性。

atomic包

image.png

pprof工具进行性能调优

参考:golang使用系列---- net/http/pprof (kingjcy.github.io)

image.png

这里我们利用样例程序进行测试

优化CPU占用

运行样例程序,执行指令运行pprof命令行窗口

image.png

然后利用top指令进行查看

image.png

看到了上面的效果,得到的列表参数含义如下:

image.png

观察上面的列表: Flat == Cum 函数中没有调用其他函数 Flat == 0 函数中只有其他函数的作用

然后我们通过上面的情况可以知道,第一个tiger.(*Tiger).Eat函数调用占用了最多的资源,我们通过list Eat来观察,结果如下:

image.png

我们可以看到,这里的问题在于,我们写了一个没有进行任何操作的循环,所以我们将这个部分注释掉,再重新运行。

也可以通过web命令可以查看调用关系图

image.png

此时注释掉之后运行,对cpu的优化很好,但是占用内存仍然很多,所以下面来优化内存的问题。

优化内存占用

这里使用图形化工具

image.png

source视图 image.png

我们在这里发现 mouse.go 当中的函数出现了问题,我们将那一句注释掉,就可以解决一部分的问题

当然内存优化还没有结束,我们还可以优化内存的其他方面

image.png

image.png

然后上面的问题就是因为申请空间之后并不使用,所以我们可以注释掉这一句,解决这里的问题。

下面是利用命令行来进行分析

image.png

goroutine 协程优化

火焰图

image.png

mutex-锁 优化

go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/mutex"

block-阻塞 优化

go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/block"

image.png

总结

image.png

关于一些问题

在利用图形化工具的时候出现了以下的问题: image.png

下面的链接有解决方式: Could not execute dot; may need to install graphviz. – SRE笔记

关于代码优化过程中自动过滤的问题:

程序正常运行,占比较小的函数会被过滤掉:

image.png

隐藏可能被过滤掉的元素可以从图形化工具当中查看。