>基准测试
- 优化代码,需要对当前代码分析
- 内置的测试框架提供了基准测试的能力
基准测试实例,以Fib函数为测试案例
go_bench.go
package test
func Fib(n int) int {
if n < 2 {
return n
}
return Fib(n-1) + Fib(n-2)
}
go_bench_test.go
package test
import "testing"
func BenchmarkFib(b *testing.B) {
for n := 0; n < b.N; n++ {
Fib(10)
}
}
测试命令:在当前测试go文件的目录终端下输入 go test -bench=BenchmarkFib
BenchmarkFib是测试的函数名称
每个基准函数被执行时都有一个不同的b.N值,这个值代表基准函数应该执行的迭代次数。
b.N从1开始,如果基准函数在1秒内就执行完了,那么b.N的值会递增以便基准函数再重新执行(译者注:即基准函数默认要运行1秒,如果该函数的执行时间在1秒内就运行完了,那么就递增b.N的值,重新再执行一次)
b.N按照近似顺序增加,每次迭代大约增长20%。基准框架试图更智能,如果它看到较小的b.N值相对较快的完成了迭代,它将b.N增加的更快。
在上面的BenchmarkFib的例子中,我们发现迭代大约5690990次耗时超过了1秒。依据此数据,基准框架计算得知,平均每次运行耗时212.2纳秒。
参考文章 Go高性能系列教程之一:基准测试
>性能优化——基准测试benchmem,提供更加详细的测试信息
go test -bench=BenchmarkFib -benchmem
在后面加上benchmem
>性能优化建议Slice
>slice预分配内存
尽可能在使用make()初始化切片时提供容量信息,就是给slice提前把容量分配好
分配之前
分配之后
性能优化了不少
>性能优化建议——Map
同Slice一样
>map预分配内存
分析
- 不断向map中添加元素的操作会触发map的扩容
- 提前分配好空间可以减少内存拷贝和Rehash的消耗
- 建议根据实际需求提前预估好需要的空间
>性能优化建议——字符串拼接
strings.Builder性能最优,用+拼接性能最差
>strings.Builder
func StrBuilder(n int, str string) string {
var builder strings.Builder
for i := 0; i < n; i++ {
builder.WriteString(str)
}
return builder.String()
}
差距很大,用Grow提前分配内存,性能更优
>bytes.Buffer
用法和strings.Builder差不多,性能也差不多
分析
- 字符串在Go语言中是不可变类型,占用内存大小是固定的
- 使用+每次都会重新分配内存
- strings.Builder,bytes.Buffer底层都是[]byte数组
-
内存扩容策略,不需要每次拼接重新分配内存
>性能优化建议-空结构体
使用空结构体节省内存
- 空结构体 struct{}实例不占据任何的内存空间
- 可作为各种场景下的占位符使用
- 节省资源
- 空结构体本身具备很强的语义,即使这里不需要任何值,仅作为占位符
使用空结构体节省内存
- 实现Set,可以考虑用map来代替
- 对于这个场景,只需要用到map的键,而不需要值
- 即使是将map的值设置为bool类型,也会多占据1个字节空间
>性能优化建议——atomic包
>使用atomic包
- 锁的实现是通过操作系统来实现,属于系统调用
- atomic操作是通过硬件实现,效率比锁高
- sync,Mutex应该用来保护一段逻辑,不仅仅用于保护一个变量
- 对于非数值操作,可以使用atomic.Value,能承载一个interface{}