Go优化浅谈

Go 优化之路

来自 GOCN.VIP 的 2020 深圳 gopher meet up,演讲者陈一枭, GitHub ,观后笔记。

前言

优化是有成本的,需要权衡优化成本和优化价值。而且随着优化的推进,收益递减,所以我们必须知道 何时停止,并且 目标明确

比如:优化目标是改进CPU,那么什么是可接受的速度,你想要将当前的性能提高多少?2倍还是10倍?如果目标是减少内存使用量,那么我们可以接受的速度有多慢,换句话说,你打算用多少速度去换取较低的需求。

停止优化的信号是什么?例如优化网络I/O服务,当我们发现Read/Write操作均消耗在syscall.Syscall则无需继续优化。

How to

1588685130857

在尝试改善一段代码性能前,我们需要先了解 当前性能

优化是一种重构形式,我们以提高性能为目标,那么就意味这要以代码可读性为代价。

既然是重构,为了确保没有破坏任何内容,所以我们需要一套全面的单元测试,以及一套很好的基准测试,用来记录你的改变对性能产生的影响。

基准测试

Go 的标准库 testing,提供了 benchmark功能,通过执行 go test -bench=. 来进行基准测试。

分析

关注不同部分的优化影响,如果将仅占 5%的例程速度提高一倍,那么整个项目的速度只提高了 2.5%,相反,你将占 80%的部分加速10%,那么整个项目将提高 8%

使用工具: GODEBUG,go tool pprof,go tool trace

校验

使用 benchstat 来统计测试,

先安装 benchstat:

go get golang.org/x/pref/cmd/benchstat

这里举个例子,看以下代码:

func MyItoa(i int) string {
	return fmt.Sprint(i) //版本一
	//return strconv.Itoa(i) //版本二
}

var r string

func BenchmarkMyItoa(b *testing.B) {
	for i := 0; i < b.N; i++ {
		r = MyItoa(i)
	}
}

分别对版本一和版本二执行:

go test -bench=. -count=10 > ver1.txt
go test -bench=. -count=10 > ver1.txt

然后进行对比:

benchstat ver1.txt ver2.txt
name      old time/op  new time/op  delta
MyItoa-4   148ns ± 2%    47ns ± 2%  -68.40%  (p=0.000 n=10+9)

可以看到,性能损耗降低了68%。

实践

使用 syncPool复用对象,复用已分配对象,减少分配数量,降低GC压力,注意重置复用对象,避免取到脏数据。

使用成员变量复用对象。

写时复制代替互斥锁。

分区:减少加锁粒度

避免包含指针结构体作为map的key,因为在GC时,运行时扫描包含指针对象并且进行追踪。解决:在插入map之前将字符串散列为整数(以下为例)。

优化前

func timeGC() {
	t := time.Now()
	runtime.GC()
	fmt.Printf("gc took: %s
", time.Since(t))
}

// go中string是指针
var pointers = map[string]int{}

func main() {
	for i := 0; i < 10000000; i++ {
		pointers[strconv.Itoa(i)] = i
	}

	for {
		timeGC()
		time.Sleep(1 * time.Second)
	}
}

优化为:

type Entity struct {
	A int
	B float64
}

var entities = map[Entity]int{}

func main() {
	for i := 0; i < 10000000; i++ {
		entities[Entity{
			A: i,
			B: float64(i),
		}] = i
	}

	for {
		timeGC()
		time.Sleep(1 * time.Second)
	}
}

使用 strings.Builder拼接字符串

避免[]bytestring的转换

多读少写,使用 sync.RWMutex代替 sync.Mutex

原文地址:https://www.cnblogs.com/Jun10ng/p/12833686.html