【问题标题】:go benchmark and gc: B/op alloc/opgo benchmark and gc: B/op alloc/op
【发布时间】:2017-02-11 04:49:53
【问题描述】:

基准代码:

func BenchmarkSth(b *testing.B) {
    var x []int
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        x = append(x, i)
    }
}

结果:

BenchmarkSth-4    50000000    20.7 ns/op    40 B/op    0 allocs/op 

问题/s:

  • 40 B/op 是从哪里来的? (非常感谢任何跟踪+说明的方式)
  • 怎么可能有 40 个 B/op 而有 0 个分配?
  • 哪一个会影响 GC 以及如何影响? (B/op 或 allocs/op)
  • 真的可以使用 append 获得 0 B/op 吗?

【问题讨论】:

    标签: testing memory go garbage-collection benchmarking


    【解决方案1】:

    The Go Programming Language Specification

    Appending to and copying slices

    可变参数函数 append 将零个或多个值 x 附加到 s 的 类型 S,必须是切片类型,并返回结果切片, 也是S型。

    append(s S, x ...T) S  // T is the element type of S
    

    如果 s 的容量不足以容纳附加值, append 分配一个新的,足够大的底层数组,适合 现有的切片元素和附加值。否则, append 重用底层数组。

    对于您的示例,平均而言,每个操作分配 [40, 41) 字节以在必要时增加切片的容量。使用摊销常数时间算法增加容量:最多 len 1024 增加到 2 倍上限,然后增加到 1.25 倍上限。平均而言,每个操作有 [0, 1) 次分配。

    例如,

    func BenchmarkMem(b *testing.B) {
        b.ReportAllocs()
        var x []int64
        var a, ac int64
        b.ResetTimer()
        for i := 0; i < b.N; i++ {
            c := cap(x)
            x = append(x, int64(i))
            if cap(x) != c {
                a++
                ac += int64(cap(x))
            }
        }
        b.StopTimer()
        sizeInt64 := int64(8)
        B := ac * sizeInt64 // bytes
        b.Log("op", b.N, "B", B, "alloc", a, "lx", len(x), "cx", cap(x))
    }
    

    输出:

    BenchmarkMem-4      50000000            26.6 ns/op        40 B/op          0 allocs/op
    --- BENCH: BenchmarkMem-4
        bench_test.go:32: op 1 B 8 alloc 1 lx 1 cx 1
        bench_test.go:32: op 100 B 2040 alloc 8 lx 100 cx 128
        bench_test.go:32: op 10000 B 386296 alloc 20 lx 10000 cx 12288
        bench_test.go:32: op 1000000 B 45188344 alloc 40 lx 1000000 cx 1136640
        bench_test.go:32: op 50000000 B 2021098744 alloc 57 lx 50000000 cx 50539520
    

    对于op = 50000000

    B/op = floor(2021098744 / 50000000) = floor(40.421974888) = 40
    
    allocs/op = floor(57 / 50000000) = floor(0.00000114) = 0
    

    阅读:

    Go Slices: usage and internals

    Arrays, slices (and strings): The mechanics of 'append'

    'append' complexity

    要使追加的 B/op(和零分配/操作)为零,请在追加之前分配一个具有足够容量的切片。

    例如,var x = make([]int64, 0, b.N)

    func BenchmarkZero(b *testing.B) {
        b.ReportAllocs()
        var x = make([]int64, 0, b.N)
        var a, ac int64
        b.ResetTimer()
        for i := 0; i < b.N; i++ {
            c := cap(x)
            x = append(x, int64(i))
            if cap(x) != c {
                a++
                ac += int64(cap(x))
            }
        }
        b.StopTimer()
        sizeInt64 := int64(8)
        B := ac * sizeInt64 // bytes
        b.Log("op", b.N, "B", B, "alloc", a, "lx", len(x), "cx", cap(x))
    }
    

    输出:

    BenchmarkZero-4     100000000           11.7 ns/op         0 B/op          0 allocs/op
    --- BENCH: BenchmarkZero-4
        bench_test.go:51: op 1 B 0 alloc 0 lx 1 cx 1
        bench_test.go:51: op 100 B 0 alloc 0 lx 100 cx 100
        bench_test.go:51: op 10000 B 0 alloc 0 lx 10000 cx 10000
        bench_test.go:51: op 1000000 B 0 alloc 0 lx 1000000 cx 1000000
        bench_test.go:51: op 100000000 B 0 alloc 0 lx 100000000 cx 100000000
    

    请注意基准 CPU 时间从大约 26.6 ns/op 减少到大约 11.7 ns/op。

    【讨论】:

    • 关于问题 3 的任何想法?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-01-03
    • 2011-08-01
    • 1970-01-01
    • 2016-11-29
    • 2014-02-22
    • 2012-03-29
    相关资源
    最近更新 更多