1. Go基准测试
Go基准测试放在xxxx_test.go这样的包下。
基准测试的函数以Benchmark来做开头
func BenchmarkXxx(b *testing.B) { ... }
1.1 testing.B
基准测试的基本原理是让用户实现一个循环来调用被测算法,外层的函数传入要循环的次数,通过不断的尝试,使得总耗时/循环次数趋于稳定后就可以认为这个值就是在这个环境下被测算法的耗时,当然也可以通过指定循环次数等使得提前结束。
外层函数通过b.N把循环次数传递过来
for n := 0; n < b.N; n ++ {
//被测算法
}
1.2 ResetTimer()
在基准测试开始之前,可能会有一些耗时的准备工作,可以使用b.ResetTimer()重新开始计时。
1.3 指令
直接指定-bench标签,其参数是正则表达式。
- 运行当前包下的所有基准测试 go test -bench=.
- 运行所有名字包含go或lang的测试 go test -bench="go|lang"
- 默认会运行单测,为了不让单测的输出影响结果,可以故意指定不存在的单测函数名 go test -bench="go|lang" -run=noExist
2. slice预分配内存
底层指向一个array,并持有len和cap两个属性,分别表示切片的长度和容量。在对切片进行append的操作时,如果切片的容量不够,就会重新分配底层的array对切片进行扩容。扩容时会涉及内存的重新分配和数据的拷贝。
所以可以通过预分配内存的方式来减少内存的分配和数据的拷贝。然后通过benchmark来测试预分配内存产生的优化效果。
特定情况下,slice预分配内存可以防止将内存分配到堆上。内存分配在栈上,当函数返回时随栈一起释放,可以减少压力。
3. map预分配内存
其原理和slice的相似,通过预分配内存来防止内存的多次分配,数据的拷贝。
心得体会(总结)
- 预估容量:在创建slice时,可以使用
make函数并为其指定容量参数。通过预估切片所需的容量,可以避免后续不断进行动态内存分配的开销。例如,如果已知需要存储100个元素的slice,可以使用make([]T, 0, 100)来创建一个初始容量为0且预留了100个元素空间的slice。 - 扩展容量:在使用
append函数添加大量元素到slice时,可能会触发内部的扩容机制,这会导致内存重新分配和元素复制。为了减少这种开销,可以一次性分配足够的内存空间,然后使用索引操作符直接赋值。这样可以避免频繁的内存分配和元素复制。 - 复用slice:如果一个slice对象不再需要使用,并且其底层数组中还有一些未被引用的元素,那么可以通过将该slice截取或重新分配更小的容量来复用内存空间。这样可以减少内存占用并提高效率。