高性能技巧 | 青训营笔记

84 阅读3分钟

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

下面相关内容仅供本人回顾使用。如有错误,烦请评论反馈,感激不尽!!!

Benchmark

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

// 一个例子
// from fib.go
func Fib(n int) int {
    if n < 2 {
        return n
    }
    return Fib(n - 1) + Fib(n - 2)
}

// from fib_test.go
func BenchmarkFib10(b *testing.B) {
    // run the Fib funciton b.N times
    for n := 0; n < b.N; n++ {
        Fib(10
    }
}

通过go test -bench=. -benchmen来进行测试

运行结果说明:

image.png

Slice预分配内存

尽可能在使用make()初始化切片的时候就提供容量信息,执行时间会差很多

究其原因是因为

  • 切片本质是一个数组片段的描述包括数字指针、片段的长度以及片段的容量
  • 切片操作并不复制切片指向的元素
  • 创建一个新的切片会复用原来切片的底层数组

image.png 另一个陷阱:大内存未释放

有一种情况,原切片由大量元素构成,但是我们在原切片的基础上切片,虽然只使用了很小一段,但底层数组在内存中仍然占据了大量的空间,得不到释放。

这个时候我们可以用copy代替re-slice

Map预分配内存

与Slice相似地,如果初始化size也可以很大程度上优化性能,分析如下:

  • 不断向map中添加元素会触发map的扩容
  • 提前分配好空间可以减少内存拷贝和Rehash的消耗

strings.Builder

在字符串拼接的过程中,使用strings.Builder往往比直接+要快,分析如下:

  • 字符串在Go语言中是不可变类型,占用内存大小是固定的
  • 使用+每次都会重新分配内存
  • strings.Builder, bytes.Buffer底层都是[]byte数组
  • 内存扩容策略,不需要每次拼接重新分配内存
// 一个strings.Builder例子
func StrBuilder(n int, str string) string {
    var builder strings.Builder
    for i := 0; i < n; i++ {
        builder.WriteString(str)
    }
    return builder.String()
}

空结构体

使用空结构体节省内存,分析如下:

  • 空结构体struct{}实例不占据任何的内存空间

  • 可作为各种场景下的占位符使用

    • 节省资源
    • 空结构体本身具备很强的语义,不需要任何值,仅作为占位符
func EmptyStructMap(n int) {
    m := make(map[int]struct{})
    for i := 0; i < n; i++ {
        m[i] = struct{}{}
    }
}

func BoolMap(n int){
    m := make(map[int]bool)
    for i := 0; i < n; i++ {
        m[i] = false
    }
}

atomic包

即原子变量与原子操作;

  • atomic提供的原子操作能够确保任意时刻只有一个goroutine对变量进行操作
  • 善用atomic能够避免程序中出现大量的锁操作
  • 锁的实现是通过操作系统来实现,属于系统调用
  • atomic操作是通过硬件实现,效率显然高

atomic的常见操作:

  • 增减
  • 载入 read
  • cas
  • 交换
  • 存储 write
var x int32 = 100
// atomic内部是一个compare ans swap, 简称cas, 会在加减操作之前先比较old new两个值再进行操作
// 而sync.Mutex应该用于保护一段逻辑,而不是仅仅一个变量
func f_add() {
	atomic.AddInt32(&x, 1)
}

func f_sub() {
	atomic.AddInt32(&x, -1)
}

func main() {
	for i := 0; i < 100; i++ {
		f_add()
		f_sub()
	}
	fmt.Printf("x: %v\n", x)
}
// 对于非数值操作,可以使用atomic.Value,能承载一个interface{}