go基准测试和垃圾回收: B/op分配/op

3

基准测试代码:

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

result:

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

问题:

  • 40 B/op是从哪里来的?(任何追踪和说明方法都将不胜感激)
  • 如何在没有分配的情况下拥有40 B/op?
  • 哪一个会影响GC,以及如何影响?(B/op还是allocs/op)
  • 使用append真的可能有0 B/op吗?
1个回答

5

The Go Programming Language Specification

Appending to and copying slices

The variadic function append appends zero or more values x to s of type S, which must be a slice type, and returns the resulting slice, also of type S.

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

If the capacity of s is not large enough to fit the additional values, append allocates a new, sufficiently large underlying array that fits both the existing slice elements and the additional values. Otherwise, append re-uses the underlying array.

针对您的示例,平均每个操作分配了 [40, 41) 字节用于在需要时增加切片的容量。使用摊销常数时间算法来增加容量:如果长度为 1024 或以下,则将其增加到原来的两倍,否则增加到原来的 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:用法和内部原理

数组、切片(和字符串):“append”的机制

'append'复杂度

为了在追加时拥有零B/op(和零allocs/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。

第三个问题有什么想法吗? - John Ballesteros

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接