go benchmark和gc:B / op alloc / op

时间:2017-02-11 04:49:53

标签: testing memory go garbage-collection benchmarking

基准代码:

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来自哪里? (非常感谢任何跟踪+指令的方式)
  • 如果有0个分配,怎么可能有40个B / op?
  • 哪一个影响GC以及如何? (B / op或allocs / op)
  • 是否真的可以使用追加0 B / op?

1 个答案:

答案 0 :(得分:4)

  

The Go Programming Language Specification

     

Appending to and copying slices

     

可变参数函数append将零或更多值x附加到s   type 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。