Выход из стенда - PullRequest
4 голосов
/ 22 мая 2019

Я вижу следующий вывод при выполнении сравнительного анализа с профилировщиком памяти

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

Я не понимаю вывод - 0 выделяет / оп, но выделено 111 B? Есть идеи, что это значит? Моя функция выделяет память в куче или нет?

1 Ответ

4 голосов
/ 22 мая 2019

Результаты теста собираются в виде типа 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 байт.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...