这是我参与「第三届青训营 -后端场」笔记创作活动的的第1篇笔记。
性能优化,简而言之,就是在不影响系统运行正确性的前提下,使之运行地更快,完成特定功能所需的时间更短。
如何评估性能
在Go语言标准库内置的testing测试包中,包含有基准测试(Benchmark)的功能,可以方便地进行性能测试。其中基准测试需要如下几个条件:
- 同其他测试文件一样,基准测试代码的文件名需要以
_test.go结尾。 - 基准测试的函数必须以
Benchmark开头。 - 基准测试函数需要接收一个指向
testing.B变量的指针。 - 测试的代码需要放在一个循环次数为
b.N次的循环中(b为上文中指向testing.B变量的指针)。
一颗例子
我们对一个计算第N个斐波那契数为例:
// fib.go
package benchmark
func Fib(n int) int {
if n < 2 {
return n
}
return Fib(n-1) + Fib(n-2)
}
接下来,我们创建一个fib_test.go文件对此函数进行基准测试:
package benchmark
import "testing"
func BenchmarkFib10(b *testing.B) {
for n := 0; n < b.N; n++ {
Fib(10)
}
}
使用go test -benchmark=.即可进行性能测试,可得到输出如下:
前面几行说明了运行的环境以及以及对应的package,最后三行则说明了测试的情况,特别是倒数第二行,我们的测试函数名为BenchmarkFib10,其后的-12则说明了我们的GOMAXPROCS,在没有特定设置的情况下,这个值同我们CPU的核数相同;第二个数表明了我们一共执行了4778167次,可认为是b.N的值;第三个数表明我们每次执行这个函数花费了242.2ns。如此一来,我们便能够从一定程度上了解到我们性能如何。
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)
}
}
可以看到预分配内存空间的Slice,能够花费更少时间。
使用Copy代替Re-Slice
在已有的切片基础上创建新的切片,不会影响新的底层数组。当我们存在一个相对较大的Slice,需要在此Slice上截取一个较小的Slice时,如果我们直接使用截取的方法,会导致原来较大的Slice因其上有引用,无法释放此较大的空间。因而我们需要使用Copy来替代Re-Slice。
Map
预分配内存
func NoPreAlloc(size int) {
data := make(map[int]int, 0)
for k := 0; k < size; k++ {
data[k] = 1
}
}
func PreAlloc(size int) {
data := make(map[int]int, size)
for k := 0; k < size; k++ {
data[k] = 1
}
}
原理同Slice类似,是由于向Map中添加元素,会触发Map的扩容,导致添加了扩容的费时,预分配好内存空间可以减少扩容的次数。
字符串
使用strings.Builder进行字符串处理
func Plus(n int, str string) string {
s := ""
for i := 0; i < n; i++ {
s += str
}
return s
}
func BuilderPlus(n int, str string) string {
var builder strings.Builder
for i := 0; i < n; i++ {
builder.WriteString(str)
}
return builder.String()
}
func BufferPlus(n int, str string) string {
buf := new(bytes.Buffer)
for i := 0; i < n; i++ {
buf.WriteString(str)
}
return buf.String()
}
可以看到使用+性能最差,strings.Builder和bytes.Buffer性能相近,但是bytes.Buffer更快。
由于在Go语言中,字符串是不可变更的数据类型,使用+时,都需要进行内存扩容,导致效率低下。