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

2 回复

但是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 // 防止编译器优化
    }
}

这种方法确保了基准测试的可重复性,同时避免了内存分配问题。基准测试的重点应该是测量搜索算法本身的性能,而不是内存分配的性能。

回到顶部