Go语言性能调优 | 豆包MarsCode AI刷题

67 阅读6分钟

01、高质量编程

1.1、什么是高质量编程

简单性:

  • 消除多余的复杂性吗,以简单清晰的逻辑编写代码
  • 不理解的代码无法修复改进

可读性:

  • 代码是给人看的,不是给机器看的
  • 编写可维护代码的第一步是确保代码可读

生产力:

  • 团队整体工作效率非常重要

1.2、编码规范

  • 代码格式
  • 注释
    • 注释应该解释代码的作用
    • 注释应该解释代码如何做的
    • 注释应该解释代码实现的原因
    • 注释应该解释代码什么情况会出错
  • 命名规范
    • variable
      • 简洁胜于冗长
      • 缩略词全大写,但是当其位于变量开头且不需要导出的时候,使用小写
      • 变量距离其被使用的地方越远,则需要携带越多的上下文信息
    • function
      • 函数名不携带包名的上下文信息,且尽量简短。
      • 当名为foo的包的某个函数返回类型是Foo的时候,可以省略类型信息而不导致歧义。
      • 当名为foo的包的某个函数返回类型是T,T不是Foo,可以在函数名中加入类型信息。
    • package
      • 只用小写字母组成,简短且包含一定上下文信息
      • 不要与标准库同名
  • 控制流程
  • 错误和异常处理
    • 简单错误:仅出现一次的错误,其他地方不需要捕获该错误
      • 优先使用errors.New来创建匿名变量来直接表示错误
      • 如果由格式化需求,使用fmt.Errorf
    • 错误的WrapUnwrap
      • 错误的Wrap实际上是提供了一个error嵌套另一个error的能力,从而生成一个error的跟踪链
      • fmt.Errorf中的使用:%w关键字来将一个错误关联至错误链中。
    • 错误判定:
      • 判断一个错误是否是特定错误,使用errors.Is不同于==, 使用该方法可以判定错误链上的所有错误是否含有特定的错误。
      • 在错误链上获取特定种类的错误,使用error.As
    • panic:不建议在业务代码中使用panic
      • 调用函数不包含recover会造成崩溃,若问题可以被屏蔽或解决,建议使用error代替panic
      • 当程序启动阶段发生不可逆转的错误时,可以在initmain函数中使用panic
    • recover
      • recover只能在被defer的函数中使用
      • 嵌套无法生效
      • 只在当前goroutine中生效
      • defer语句是后进先出的
      • 一般recover后在log中记录当前的调用栈

1.3、性能优化建议

  1. slice预分配内存, 尽可能在使用make初始化切片时提供容量信息。

  2. 大内存未释放:在已有切片的基础上创建切片,不会创建新的底层数组,如果原来的切片比较大,代码在原来的基础上新建小切片,这时候底层数组在内存中有引用,得不到释放。可以用copy代替re-slice

  3. map预分配内存。

  4. 字符串拼接使用strings.Builder, 如果已知大小,可以使用Grow预分配内存。

  5. 空结构体节省内存

    1. 空结构体示例不占据任何内存空间

    2. 可以作为各种场景下的占位符使用

      1. 节省资源

      2. 空结构体本身具备很强的语义,即这里不需要任何值,仅作为占位符。

      3. func EmptyStructMap(n int) {
            m := make(map[int]struct{})
            
            for i := 0; i < n; i ++ {
                m[i] = struct{}{}
            }
        }
        
      4. 空结构体map可以实现set

  6. 如何使用atomic包:

    1. type atomicCounter struct{
          i int32
      }
      func AtomicAddOne(c *atomicCounter) {
          atomic.AddInt32(&c, i, 1)
      }
      
    2. 上面代码比加锁的效果好,因为atomic是通过硬件实现的,锁是操作系统来实现的。

02、性能调优实战

2.1、性能优化分析工具 pprof

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

image-20241109164449388.png

简单使用,首先从github.com/wolfogre/go… 下载下来调试代码,然后运行。

点击http://localhost:6060/debug/pprof/ 查看有哪些属性可以看。

2.1.2、查看CPU

cmd中切换到项目所在位置,然后输入下面命令:

 go tool pprof "http://localhost:6060/debug/pprof/profile?seconds=10"

10秒钟,会进入pprof环境,然后输入top查看耗时的代码:

image-20241109154714024.png

这里解释一下各个字段的含义:

字段名含义
flat代码自己的运行时间(不算调用别的程序)
flat%代码运行时间占cpu总时间的比例
sum%统计前面几行的cpu使用率之和
cum代码运行时间,包括调用别的程序的时间
cum%代码运行时间占总时间的比例

考虑一下什么时候flat=cum; flat=0flat = cum;\ flat = 0

  • flat=cumflat = cum:代码没有调用别的程序
  • flat=0flat=0 代码一直在调用别的程序

如果要定位到哪一行代码出错,可以使用list xx

image-20241109155149098.png

如果不想使用命令行看,可以使用web命令:

go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/profile?seconds=10"

等运行完之后,会自动打开浏览器,小伙伴们需要先去下载一下Graphviz ,安装之后将其bin目录的全路径配置到环境变量中即可。

2.2.2、查看堆内存

cmd中切换到项目所在位置,然后输入下面命令:

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

点击左上角的VIEW可以切换到其他视图,如top

image-20241109160613092.png

source视图,类似于上面的list命令:

image-20241109160653143.png

左上角的sample可以切换采样

image-20241109160917065.png

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

例如,切换到alloc_space状态下,可以发现还有一个地方申请大内存,因为没有用到,被gc了,所以不显示在inuse采样中

image-20241109161305701.png

定位到这一行代码:

image-20241109161414216.png

注释掉即可

2.2.3、Goroutine

cmd中切换到项目所在位置,然后输入下面命令:

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

切换到Flame Graph视图下,从上到下表示调用顺序, 长度表示了这个代码占用cpu的时间:

image-20241109161740938.png

右击某一个即可进入源码查看哪部分有问题,注释掉即可。

2.2.4、mutex 锁

cmd中切换到项目所在位置,然后输入下面命令:

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

image-20241109163434979.png

2.2.5、block

cmd中切换到项目所在位置,然后输入下面命令:

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

......

不可视化的时候:

go tool pprof "http://localhost:6060/debug/pprof/block"

可以看到有一些程序被过滤掉了:

image-20241109164239399.png

http://localhost:6060/debug/pprof/ 下面的block条目下,可以查看隐藏的内容。