这是我参与「第五届青训营 」伴学笔记创作活动的第 3 天
Benchmark的使用
Benchmark(基准测试)是在一定的工作条件之下检测程序性能的一种方法。
使用示例
fib.go
package fib
func fib(n int) int {
if n <= 1 {
return 1
}
return fib(n-1) + fib(n-2)
}
fib_test.go
package fib
import "testing"
func BenchmarkFib(b *testing.B) {
for i := 0; i < b.N; i++ {
fib(10)
}
}
运行测试
go test -bench=. -benchmem
测试结果
goos: darwin
goarch: arm64
pkg: fib
BenchmarkFib-8 6039063 181.3 ns/op 0 B/op 0 allocs/op
PASS
ok fib 1.405s
go test命令参数:
-bench regexp运行和regexp匹配的Benchmark-benchtime t运行足够的次数,直到运行时间达到t-count n每个test、benchmark、fuzz重复运行n次-cpu 1,2,4对每个test、benchmark、fuzz指定一个GOMAXPROCS值的列表,每个测试会以列表中的每个GOMAXPROCS值各执行一次。-benchmem打印内存分配统计信息
slice
slice本质是数组片段的一个描述,包括数组指针、片段长度、容量。
切片操作不会复制切片指向的元素
创建一个新的切片会复用原来切片的底层数组
-
尽可能在使用make()初始化slice时提供容量信息,避免过多次数的内存分配
make([]T, len, cap)向slice中添加元素时,若slice容量不够,会创建一个更大的新的底层数组,将原切片中的元素复制过去。预先分配容量可减少重新分配内存的次数
-
使用copy代替re-slice
在一个切片的基础上创建切片,不会重新创建底层数组,如果原切片较大,新切片较小,小切片会始原数组在内存中有引用,得不到释放。
Map
-
map预分配内存
make(map[K]V, size)可减少内存拷贝和rehash的消耗
字符串处理
-
使用strings.Builder拼接字符串
-
使用bytes.Buffer拼接字符串
字符串是不可变类型,使用+拼接每次会重新分配内存。strings.Builder、bytes.Buffer底层都是[]byte数组,按一定的策略扩容,不需要每次拼接都重新分配内存。
- 将bytes.Buffer转化为字符串时会重新申请一块内存,而strings.Builder直接将底层的[]byte转化成字符串类型返回,性能要好一些。
- 如果已知最终字符串长度,可
builder.Grow(len)、buff.Grow(len),一次性分配内存
空结构体
- 使用空结构体节省内存
空结构体struct{}实例不占用任何内存,可作为一些场景下的占位符
用map实现set,用空结构体作为值
atomic包
多个线程需要使用同一个变量时
-
加锁
type mutexCounter struct { i int32 m sync.Mutex } func MutexAddOne(c *mutexCounter) { c.m.Lock() c.i++ c.m.Unlock() }
-
使用atomic包
type atomicConter struct { i int32 } func AtomicAddOne(c *atomicCounter) { atomic.AddInt32(&c.i, 1) }锁的实现是通过操作系统来实现,属于系统调用,atomic包操作是通过硬件实现,效率比锁高。
锁应该用来保护一段逻辑,不仅仅用于保护一个变量。
对于非数值操作,可以使用atomic.Value,能承载一个interface{}