Golang内存不足问题:基准测试运行内存溢出解决方案
Golang内存不足问题:基准测试运行内存溢出解决方案 我在基准测试函数中分配了一个大小为 b.N 的整数切片。
当 b.N 为以下值时一切正常: 10000 1000000 100000000
然而当 b.N 更大时,我遇到了: 运行时堆栈: runtime.throw(0x6dac98, 0x16) /usr/local/go/src/runtime/panic.go:616 +0x81 runtime.sysMap(0xc4504b0000, 0x3b9ad0000, 0x0, 0x876f98) /usr/local/go/src/runtime/mem_linux.go:216 +0x20a runtime.(*mheap).sysAlloc(0x85c7a0, 0x3b9ad0000, 0x0) /usr/local/go/src/runtime/malloc.go:470 +0xd4 runtime.(*mheap).grow(0x85c7a0, 0x1dcd65, 0x0) /usr/local/go/src/runtime/mheap.go:907 +0x60 runtime.(*mheap).allocSpanLocked(0x85c7a0, 0x1dcd65, 0x876fa8, 0x0) /usr/local/go/src/runtime/mheap.go:820 +0x301 runtime.(*mheap).alloc_m(0x85c7a0, 0x1dcd65, 0xffffffffffff0101, 0x7f959b7fddb8) /usr/local/go/src/runtime/mheap.go:686 +0x118 runtime.(*mheap).alloc.func1() /usr/local/go/src/runtime/mheap.go:753 +0x4d runtime.(*mheap).alloc(0x85c7a0, 0x1dcd65, 0x10101, 0x6e9a28) /usr/local/go/src/runtime/mheap.go:752 +0x8a runtime.largeAlloc(0x3b9aca000, 0x450101, 0x456000) /usr/local/go/src/runtime/malloc.go:826 +0x94 runtime.mallocgc.func1() /usr/local/go/src/runtime/malloc.go:721 +0x46 runtime.systemstack(0x0) /usr/local/go/src/runtime/asm_amd64.s:409 +0x79 runtime.mstart() /usr/local/go/src/runtime/proc.go:1175
该怎么办?我能告诉它不要让 b.N 那么高吗?
被基准测试的代码是一个在数组中搜索的方法。我随机生成了这个大小为 b.N 的数组。我应该改变这个策略吗?如何对这个搜索函数进行基准测试?
更多关于Golang内存不足问题:基准测试运行内存溢出解决方案的实战教程也可以访问 https://www.itying.com/category-94-b0.html
但是N难道不是测试应该运行的次数吗?https://golang.org/pkg/testing/ 中有这样的说明
形式为
func BenchmarkXxx(*testing.B)
的函数被视为基准测试,当提供 -bench 标志时由 “go test” 命令执行。基准测试按顺序运行。
关于测试标志的描述,请参阅 https://golang.org/cmd/go/#hdr-Testing_flags
示例基准测试函数如下所示:
func BenchmarkHello(b *testing.B) {
for i := 0; i < b.N; i++ {
fmt.Sprintf("hello")
}
}
基准测试函数必须将目标代码运行 b.N 次。在基准测试执行期间,b.N 会被调整直到基准测试函数持续足够长的时间以便可靠计时
而不是分配多少内存。当然,你可以在基准测试中分配不同大小的切片,但不是通过使用 N 来实现。
更多关于Golang内存不足问题:基准测试运行内存溢出解决方案的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
这是一个典型的基准测试内存管理问题。当 b.N 值过大时,Go 运行时无法分配所需的大块连续内存,导致堆栈溢出。
问题分析
在基准测试中分配大小为 b.N 的切片会导致内存需求呈线性增长。当 b.N 达到数亿级别时,内存需求可能超过系统可用连续内存。
解决方案
1. 使用固定大小的测试数据
不要依赖 b.N 来决定测试数据大小,而是使用固定的、合理大小的测试数据集:
func BenchmarkSearch(b *testing.B) {
// 使用固定大小的测试数据
const testSize = 1000000
data := make([]int, testSize)
for i := range data {
data[i] = rand.Intn(testSize)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
target := rand.Intn(testSize)
search(data, target)
}
}
2. 重用测试数据
在多个基准测试运行中重用相同的测试数据:
var benchmarkData []int
func init() {
benchmarkData = make([]int, 1000000)
for i := range benchmarkData {
benchmarkData[i] = rand.Intn(1000000)
}
}
func BenchmarkSearch(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
target := rand.Intn(len(benchmarkData))
search(benchmarkData, target)
}
}
3. 使用子基准测试测试不同数据规模
如果需要测试不同数据规模下的性能,使用子基准测试:
func BenchmarkSearch(b *testing.B) {
sizes := []int{1000, 10000, 100000, 1000000}
for _, size := range sizes {
b.Run(fmt.Sprintf("Size-%d", size), func(b *testing.B) {
data := make([]int, size)
for i := range data {
data[i] = rand.Intn(size)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
target := rand.Intn(size)
search(data, target)
}
})
}
}
4. 使用 b.RunParallel 进行并行基准测试
对于可并行的搜索算法:
func BenchmarkSearchParallel(b *testing.B) {
const testSize = 1000000
data := make([]int, testSize)
for i := range data {
data[i] = rand.Intn(testSize)
}
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
target := rand.Intn(testSize)
search(data, target)
}
})
}
5. 控制基准测试内存使用
通过环境变量限制基准测试的内存使用:
go test -bench=. -benchmem -memprofile=mem.prof
推荐的基准测试模式
func BenchmarkSearch(b *testing.B) {
// 预分配测试数据
const dataSize = 1000000
testData := make([]int, dataSize)
for i := 0; i < dataSize; i++ {
testData[i] = i // 或使用随机数据
}
// 重置计时器,排除准备数据的耗时
b.ResetTimer()
// 运行基准测试
for i := 0; i < b.N; i++ {
// 在数据范围内随机选择目标值
target := i % dataSize
result := search(testData, target)
_ = result // 防止编译器优化
}
}
这种方法确保了基准测试的可重复性,同时避免了内存分配问题。基准测试的重点应该是测量搜索算法本身的性能,而不是内存分配的性能。

