这是我参与「第五届青训营」伴学笔记创作活动的第 7 天
下面相关内容仅供本人回顾使用。如有错误,烦请评论反馈,感激不尽!!!
Benchmark
性能表现需要实际数量来衡量,Go语言提供了支持基准性能测试的benchmark工具
// 一个例子
// from fib.go
func Fib(n int) int {
if n < 2 {
return n
}
return Fib(n - 1) + Fib(n - 2)
}
// from fib_test.go
func BenchmarkFib10(b *testing.B) {
// run the Fib funciton b.N times
for n := 0; n < b.N; n++ {
Fib(10
}
}
通过go test -bench=. -benchmen来进行测试
运行结果说明:
Slice预分配内存
尽可能在使用make()初始化切片的时候就提供容量信息,执行时间会差很多
究其原因是因为
- 切片本质是一个数组片段的描述包括数字指针、片段的长度以及片段的容量
- 切片操作并不复制切片指向的元素
- 创建一个新的切片会复用原来切片的底层数组
另一个陷阱:大内存未释放
有一种情况,原切片由大量元素构成,但是我们在原切片的基础上切片,虽然只使用了很小一段,但底层数组在内存中仍然占据了大量的空间,得不到释放。
这个时候我们可以用copy代替re-slice
Map预分配内存
与Slice相似地,如果初始化size也可以很大程度上优化性能,分析如下:
- 不断向map中添加元素会触发map的扩容
- 提前分配好空间可以减少内存拷贝和Rehash的消耗
strings.Builder
在字符串拼接的过程中,使用strings.Builder往往比直接+要快,分析如下:
- 字符串在Go语言中是不可变类型,占用内存大小是固定的
- 使用
+每次都会重新分配内存 strings.Builder, bytes.Buffer底层都是[]byte数组- 内存扩容策略,不需要每次拼接重新分配内存
// 一个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()
}
空结构体
使用空结构体节省内存,分析如下:
-
空结构体
struct{}实例不占据任何的内存空间 -
可作为各种场景下的占位符使用
- 节省资源
- 空结构体本身具备很强的语义,不需要任何值,仅作为占位符
func EmptyStructMap(n int) {
m := make(map[int]struct{})
for i := 0; i < n; i++ {
m[i] = struct{}{}
}
}
func BoolMap(n int){
m := make(map[int]bool)
for i := 0; i < n; i++ {
m[i] = false
}
}
atomic包
即原子变量与原子操作;
- atomic提供的原子操作能够确保任意时刻只有一个goroutine对变量进行操作
- 善用atomic能够避免程序中出现大量的锁操作
- 锁的实现是通过操作系统来实现,属于系统调用
- atomic操作是通过硬件实现,效率显然高
atomic的常见操作:
- 增减
- 载入 read
- cas
- 交换
- 存储 write
var x int32 = 100
// atomic内部是一个compare ans swap, 简称cas, 会在加减操作之前先比较old new两个值再进行操作
// 而sync.Mutex应该用于保护一段逻辑,而不是仅仅一个变量
func f_add() {
atomic.AddInt32(&x, 1)
}
func f_sub() {
atomic.AddInt32(&x, -1)
}
func main() {
for i := 0; i < 100; i++ {
f_add()
f_sub()
}
fmt.Printf("x: %v\n", x)
}
// 对于非数值操作,可以使用atomic.Value,能承载一个interface{}