这是我参与「第五届青训营 」笔记创作活动的第3天
性能优化
基准测试
使用benchmark工具,具体指令如下:
go test -bench=. -benchmem
优化建议
slice
优化方法
声明时优化
首先,我们来看go底层的实现:
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
- 通过上面代码,我们可以知道切片底层是一个指向数组的一个指正加上长度和容量的结构体
- 所以在我们进行append的时候,如果容量不够的话,就会有扩容操作,这个时候就会耗时,所以就会导致效率低一些
- 所以如果我们知道slice所需要的容量的话,我们在声明时就设置cap大小,这样就不会有扩容操作了
在原有切片获取新切片优化
因为slice底层是一个指向底层数组的指针,所以我们对切片进行切片的话得到的data地址改变,但是原有的大切片空间并没有被释放,所以我们可以使用copy代替
扩展
下面是slice的扩容策略:
-
如果新申请的容量大于2倍的旧容量,则最终容量是新申请的容量
-
如果旧切片的长度小于1024,则最终容量是就容量的2倍
-
如果旧切片的长度大于等于1024,则最终容量从旧容量开始循环增加原来的1/4,即newcap = oldcap for {newcap += newcap / 4},直到最终容量大于或等于新申请的容量为止
-
如果最终容量计算值溢出,即超过了int的最大范围,则最终容量就是新申请容量
-
扩容后新的切片不一定拥有新的地址(非指针类型不会拥有新地址)
map
优化方法
声明时优化
- 和slice同理,go中的map也有大小和扩容的概念,所以我们知道map所需要的大小时,我们在声明时就map的size,这样就不会有扩容操作了
- 其次,go中的map在扩容时会有新桶的建立,这时候就涉及到旧桶的rehash然后重新放入key-value对的操作,所以我们尽量不让map扩容,也就是声明足够的size
string
优化方法
stirng的拼接时,尽量使用strings.Builder和bytes.Buffer,原因如下:
- 字符串在 Go 语言中是不可变类型,大小是固定的,当使用+拼接2个字符串时,生成一个新的字符串,那么就需要开辟一段新的空间
- strings.Builder 和 bytes.Buffer 底层都是 []byte 数组
空结构体
优化方法
在go中空结构体不占据内存空间,所以我们可以用空结构体和map轻松实现set
atomic
- atomic为什么快:锁的实现是通过操作系统来实现,属于系统调用,atomic操作是通过硬件实现的
- 但是atomic只用来保护一个变量,而sync.Mutex用来保护一段逻辑,所以在使用时需要考虑实际情况