Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Output from benchmem

I see following output when running benchmarking with memory profiler

SomeFunc             100      17768876 ns/op         111 B/op          0 allocs/op

I don't understand the output - 0 allocs/op but there are 111 B allocated? Any idea what it means? Does my function allocate memory on heap or not?

like image 678
Kris Kwiatkowski Avatar asked May 22 '19 10:05

Kris Kwiatkowski


1 Answers

The benchmark results are gathered in a value of type testing.BenchmarkResult:

type BenchmarkResult struct {
        N         int           // The number of iterations.
        T         time.Duration // The total time taken.
        Bytes     int64         // Bytes processed in one iteration.
        MemAllocs uint64        // The total number of memory allocations; added in Go 1.1
        MemBytes  uint64        // The total number of bytes allocated; added in Go 1.1
}

And the values you see for the allocated memory and allocations per op are returned by BencharkResult.AllocedBytesPerOp() and BenchmarkResult.AllocsPerOp(). They document that the return values are:

AllocedBytesPerOp returns r.MemBytes / r.N.

AllocsPerOp returns r.MemAllocs / r.N.

So the result is an integer division. Which means that if a benchmarked function performs different number of allocations in different calls, the result may not be an integer, but the fraction part is thrown away (this is how integer division works).

So if in average a function performs less than 1 allocations, you will see 0 allocs/op, but the allocated memory may be greater than 0 if its average is at least 1 byte per call.

Let's see an example:

var (
    counter   int
    falseCond bool // Always false at runtime
)

func AvgHalfAllocs() {
    counter++
    if counter%2 == 0 {
        return
    }
    buf := make([]byte, 128)
    if falseCond {
        fmt.Println(buf)
    }
}

func AvgOneAndHalfAllocs() {
    for i := 0; i < 3; i++ {
        AvgHalfAllocs()
    }
}

Here AvgHalfAllocs() does half allocation per call on average, it does it by returning from half of the calls without allocating anything, and doing exactly 1 allocation in the other half of the calls.

AvgOneAndHalfAllocs() does 1.5 allocations per call on average, because it calls AvgHalfAllocs() 3 times.

The purpose of the falseCond variable and the fmt.Println() call is just so the compiler does not optimize away our allocation, but fmt.Println() will never be called, so it won't interfere with the allocations.

Benchmarking the above 2 functions like this:

func BenchmarkAvgHalfAllocs(b *testing.B) {
    for i := 0; i < b.N; i++ {
        AvgHalfAllocs()
    }
}

func BenchmarkAvgOneAndHalfAllocs(b *testing.B) {
    for i := 0; i < b.N; i++ {
        AvgOneAndHalfAllocs()
    }
}

And the results are:

BenchmarkAvgHalfAllocs-4          50000000    29.2 ns/op    64 B/op   0 allocs/op
BenchmarkAvgOneAndHalfAllocs-4    20000000    92.0 ns/op   192 B/op   1 allocs/op

As you can see, 0.5 allocations per call is truncated to 0 in case of AvgHalfAllocs(), and 1.5 is truncated to 1 in case of AvgOneAndHalfAllocs().

The average allocated memory in case of AvgHalfAllocs() is 0.5 * 128 bytes = 64 bytes.

The average allocated memory in case of AvgOneAndHalfAllocs() is 1.5 * 128 bytes = 192 bytes.

like image 91
icza Avatar answered Oct 07 '22 11:10

icza