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?
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With