高质量编程与性能调优|青训营笔记

56 阅读3分钟

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

1 高质量编程

1、高质量

高质量:正确可靠、简洁清晰

边界条件、异常处理、易读易维护

编程原则:简单、可读、生产力

2、编码规范

  • 代码格式

    gofmt自动格式化代码

  • 注释

    作用、实现过程、实现原因、什么情况会出错

    变量、常量、函数、结构

  • 命名规范

    • 变量:缩略词全大写,但位于变量开头且不需要导出是全小写

    • 函数:函数名不携带包名的上下文信息;尽量简短;返回类型T不是Foo时,可以加入类型信息

      http包中:
      1. func Serve(I net.Listener, handler Handler) err
      2. func ServeHTTP(I net.Listener, handler Handler) err
      
      http.serve         √
      heep.serverHTTP    ×
      
    • 包:小写,不含大写和下划线等;简短且包含一定的上下文信息;不要与标准库同名

  • 控制流程

    避免复杂嵌套

  • 错误和异常处理

    • 简单错误:errors.New、fmt.Errorf
    • 错误的Wrap Unwrap
    • 错误判定
      • errors.Is:判定一个错误是否为特定错误
      • errors.As:在错误链上获取指定种类的错误
    • panic
      • 不建议使用
      • 调用函数不包含recover会造成程序崩溃
      • 若问题可以被屏蔽或解决,建议使用error或panic替代
      • 当程序在启动阶段发生不可逆转的错误时,可以在init或main函数中使用panic
    • recover
      • 只能在defer的函数中使用
      • 嵌套无法生效
      • 只在当前goroutine生效
      • defer的语句是后进先出的
      • 如果需要等多的上下文信息,可以在recover后再在og中记录当前的调用栈
    • defer语句会在函数返回前调用,多个defer语句是后进先出的

2 性能调优实战

1、Benchmark

性能需要实际数据来衡量,可以使用奔驰玛瑞克工具进行基准性能测试

go test -bench=. -benchmem

2、Slice

  • Slice预分配内存:尽可能使用make()初始化切片时提供容量信息

    data := make([]int, 0, size)

  • 切片本质是一个数组片段的描述,包括数组指针、片段长度、片段容量(不改变内存分配情况下的最大长度)

    type slice struct {
    	array unsafe.Pointer
    	len int
    	cap int
    }
    
  • 切片操作并不复制切片指向的元素

  • 创建一个新的切片会复用原来切片的底层数组

  • 大内存释放:在已有切片的基础上创建切片,不会创建新的底层数组

    • 应用场景:原切片较大,代码在原切片基础上新建小切片;底层数组在内存中有引用,得不到释放
    • 可使用copy代替reslice

    go test -run=. -v

3、Map

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

4、字符串处理

  • 使用strings.Builder

    • 使用+拼接性能很差,strings.Builder,bytes.Buffer相近,strings.Buffer更快

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

    • strings.Builder直接将底层的[]byte转换成了字符串类型返回

      func (b *Buffer) String() string {
      	if b == nil {
      	return "<nil>"
      	}
      	return string(b.buf[b.off:])
      }
      
      func (b *Builder) String() string {
          return *(*string)(unsafe.Pointer(&b.buf))
      }
      

5、空结构体

使用空结构体节省内存

  • 空结构体struct{}实例不占据任何的内存空间,可以作为各种场景下的占位符使用

    • 节省资源;语义强,仅作为占位符
  • 实现Set,可以考虑用map来代替

    • 只需要用到map的键,而不需要值

6、atomic包

锁的实现是通过操作系统来实现的,属于系统调用

atomic操作是通过硬件实现,效率比锁高

sync.Mutex用来保护一段逻辑,不仅仅保护一个变量

对于非数值操作,可使用atomic.Value,能承载一个interface{}