Результаты теста собираются в виде типа 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
}
И значения, которые вы видите для выделенной памяти и распределений для операции, возвращаются BencharkResult.AllocedBytesPerOp()
и BenchmarkResult.AllocsPerOp()
. Они документируют, что возвращаемые значения:
AllocedBytesPerOp возвращает r.MemBytes / r.N .
AllocsPerOp возвращает r.MemAllocs / r.N .
Таким образом, результатом является целочисленное деление. Это означает, что если тестируемая функция выполняет различное количество распределений в разных вызовах, результатом может быть не целое число, а часть дроби, которая отбрасывается (так работает целочисленное деление).
Таким образом, если в среднем функция выполняет менее 1 выделения, вы увидите 0 allocs/op
, но выделенная память может быть больше 0, если ее среднее значение составляет не менее 1 байта на вызов.
Давайте рассмотрим пример:
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()
}
}
Здесь AvgHalfAllocs()
в среднем выполняет половину распределения на вызов, делает это, возвращая половину вызовов без выделения ресурсов и выполняя ровно 1 распределение в другой половине вызовов.
AvgOneAndHalfAllocs()
делает в среднем 1,5 распределения на вызов, потому что он вызывает AvgHalfAllocs()
3 раза.
Назначение переменной falseCond
и вызова fmt.Println()
состоит в том, чтобы компилятор не оптимизировал наше выделение, но fmt.Println()
никогда не будет вызываться, поэтому он не будет мешать распределению .
Сравнительный анализ вышеупомянутых 2 функций, таких как:
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()
}
}
И результаты:
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
Как видите, 0,5 распределения на вызов усекаются до 0
в случае AvgHalfAllocs()
, а 1,5 - до 1 в случае AvgOneAndHalfAllocs()
.
Среднее выделенное количество памяти в случае AvgHalfAllocs()
составляет 0,5 * 128 байтов = 64 байта.
Средний размер выделенной памяти в случае AvgOneAndHalfAllocs()
составляет 1,5 * 128 байт = 192 байт.