Go语言性能调优| 青训营笔记

91 阅读4分钟

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

性能优化建议

Benchmark

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

func Fib(n int) int {
if n < 2{
return n
}
return Fib(n-1) + Fib(n-2)
} 


func BenchmarkFib10(b *testing.B) {
for n := 0; n < b.N; n++{
	Fib(10)
}
}

运行benchmark:

go test -bench=. -benchmem

运行结果:

image-20230219100031715.png

显示内容从上到下依次为,

BenchmarkFib10测试函数名
-8 CPU核数
4745695 执行的总次数
251.0 ns/op 每次CPU执行花费的时间
0B/op每次执行申请的内存
0 allocs/op 每次执行申请几次内存

slice

slice语句建议预分配内存,在使用make()初始化切片时提供容量信息

func NoPreAlloc(size int){
	data:=make([]int, 0)
	for k :=0; k< size; k++{
	data = append(data, k)
	}
}


func PreAlloc(size int){
	data:=make([]int, 0, size)
	for k:=0; k<size; k++{
	data = append(data, k)
	}
}

运行结果:

image-20230219100930930.png

image-20230219100957502.png

可以看出,预分配每次运行时间明显少于未预分配空间的方法,而且执行申请内存也减少。

产生差异的原因是切片是数组片段的描述,包括数组指针,片段长度和容量,切片操作不复制切片指向的元素,而创建新的切片复用原来的底层数组,所以在已有切片基础和是哪个创建切片,不会创建新的底层数组。而对于在已有切片基础上创建新切片场景,导致原底层数组在内存中有引用得不到释放,进而影响性能。此时可以用copy代替re-slice。

map

map预分配内存可以优化性能 ,其原因类似slice, 因为不断向map中添加元素会触发map扩容,而提前分配好空间可以有效减少内存拷贝和rehash的消耗。

字符串处理

常见字符串拼接方式有:直接用'+'连接; stringsBuilder;bytes.Buffer。对如上几种方式进行字符串拼接操作,列举案例如下:

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


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


func ByteBuffer(n int, str string) string{
	buf := new(bytes.Buffer)
	for i := 0; i < n; i++{
		builder.WriteString(str)
	}
	return builder.String()
}

运行结果:

image-20230219102349709.png 由结果分析可得,使用strings.Builder性能和bytes.buffer接近,而且都比+快。原因如下:首先因为因为字符串在Go中占用内存大小固定,而+每次都会重新分配内存;而另外两个使用的底层是[]byte,有内存扩容策略,不需要每次拼接重新分配内存。

而byte.buffer转化为字符串时重新申请了空间,而string.Builder直接将底层[]byte转换成了字符串返回,所以会更快。

更进一步的性能优化建议是,在使用上述两种方法时也预先分配内存空间大小。

空结构体

空结构体struct{}实例不占据任何内存空间,可以作为占位符使用,能够节省资源而且具有很强的语义。例如只需要用map代替实现set,值设置为空结构体能够有效节省内存。

atomic包

一般锁的实现是通过操作系统调用来实现,而atomic操作是通过硬件实现,效率比锁搞,sync.Mutex应该用来保护一段逻辑而不是仅仅用于保护一个变量,对于非数值操作可以用atomic.Value来承载interface{}。

小结:避免常见性能陷阱可以保证大部分程序的性能,而且不要一味追求程序的性能,越高级的性能优化手段越容易出现问题。

性能分析工具

性能优化要依靠数据而不是猜测,用统一标准评估,服务优化要定位最大瓶颈而不是细枝末节,不要过早优化,因为产品在不断迭代优化,部分要求可能被更新,预期在即将出现性能问题后进行分析,不能过度优化,部分优化手段在修改需求后可能无法兼容。

性能分析工具pprof是能够可视化和简单性能分析的工具。基于github.com/wolfogre/go… 该项目埋入了炸弹代码,可以产生可观测的性能问题。性能分析主要针对CPU,堆内存,协程goroutine,锁Mutex,阻塞Block等方面进行展开。pprof提供可视化终端来分析代码性能。