性能优化指南
这是我在字节跳动青训营学习的第34天,也是我参加《第六届青训营笔记伴读》的第七篇笔记
在优化性能之前,可以使用benchmark来查看性能表现 使用benchmark时,需要函数名由Benchmark开头,且参数为*testing.B,文件名后缀为_test
go
go test -bench . -benchmem
性能优化建议
slice 预分配内存
-
尽可能使用make()初始化切片时提供容量信息
-
slice本质是一个数组片段的描述
- 包括数组指针
- 片段的长度
- 片段的容量(不改变内存分配情况下的最大长度)
-
切片操作并不复制切片指向的元素
-
创建一个新的切片会复用原来切片的底层数组
内存陷阱
在已有切片的基础上创建切片,不会创建新的底层数组,在这种情况下可以使用copy代替re-slice
map 预分配内存
- 不断向map中添加元素的操作会触发map的扩容
- 提前分配好的空间可以减少内存拷贝和Rehash的小号
- 建议根据实际需求提前预估好需要的空间
使用Strings Builder
- 常见的字符串拼接方式
go
//直接拼接
func Plus(n int, str string) string {
s := ""
for i := 0; i < n; i++ {
s += str
}
return s
}
go
//使用Builder
func StrBuilder(n int, str string) string {
var builder strings.Builder
for i := 0; i < n; i++ {
builder.WriteString(str)
}
return builder.String()
}
}
go
// 使用buffer
func ByteBuffer(n int, str string) string {
buf := new(bytes.Buffer)
for i := 0; i < n; i++ {
buf.WriteString(str)
}
return buf.String()
}
}
可以看到Builder和buffer性能比string快了10倍
使用空结构体节省内存
- 空结构体 Struct{}实例不占据任何内存空间
- 对于Set,可以考虑用map来代替
- 对于这个场景,只需要用到map的键,而不需要值
- 即使是将map的值设置为bool类型,也会占据1个字节
使用atomic包
对于多线程场景,除了使用加锁的方式,还可以使用atomic包时间性能会好很多
- 锁的视线是通过操作系统实现,属于系统调用
- atomic操作是通过硬件实现,效率比锁高
- sync.Mutex应该用来保护一段逻辑,不仅仅用于保护一个变量
- 对于非数值操作,可以使用atomic.Value,能承载一个interface{}
小结
- 避免常见的性能陷阱可以保证大部分程序的性能
- 普通的应用代码,不要一味的追求程序的性能
- 越高级的性能优化手段越容易出问题
- 在满足正确可靠,简洁清晰的质量要求的前提下提高程序性能