Задать вопрос
@VladimirFarshatov

Go не верное выделение памяти для []byte или ошибка в go test benchmark?

Смотрим стандартный логгер из пакета log. В его структуре находится поле(свойство) buf:
type Logger struct {
	mu        sync.Mutex // ensures atomic writes; protects the following fields
	prefix    string     // prefix on each line to identify the logger (but see Lmsgprefix)
	flag      int        // properties
	out       io.Writer  // destination for output
	buf       []byte     // for accumulating text to write
	isDiscard int32      // atomic boolean: whether out == io.Discard
}

Функция log.New() также не инициализует это поле и оно остается nil, что видно под дебаггером.

Если запустить вот такой бенчмарк с -benchmem:
var stdLgr = log.New(bufWriter, "", log.Ldate)
func Benchmark_Stdlog(b *testing.B) {
	for i := 0; i < b.N; i++ {
		bufWriter.Reset()
		stdLgr.Printf("any info message")
	}
}

То получаем:
Benchmark_Stdlog-4 360367 2898 ns/op 16 B/op 1 allocs/op

, где наглядно видно что логгер делает только одну аллокацию и только для операции fmt.Sprintf() внутри себя. Хотя под дебагером видно что слайс buf наращивает свою длину согласно принятому в Go алгоритму: 8, 16,32,64 байта.

Это ошибка бенчмарка, что он не видит аллокаций для полей структур типа []byte (возможно и иных слайсов!) или это особенное поведение компилятора Go для неинициализированного слайса байт?

Кстати, если заменить поле логгера на глобальный буфер этого же типа:
var buf []byte
то бенчмарк точно также не видит аллоцирование памяти функцией append()!

Кто-то может объяснить что происходит?
  • Вопрос задан
  • 140 просмотров
Подписаться 1 Средний Комментировать
Пригласить эксперта
Ответы на вопрос 2
@VladimirFarshatov Автор вопроса
Да, тестировал на Go 1.19.1, сейчас проверил на версии 1.20.0 всё ровно тоже самое. :(

Всё больше склоняюсь к тому, что бенчмарк "дырявый" и доверять его показаниям - слишком наивно.
Ответ написан
@falconandy
В коде логгера поле buf "reset-ится": l.buf = l.buf[:0]
переиспользуя уже выделенную до этого память.
В данном примере, который вы тестируете, длина строки не увеличивается и выделение памяти для buf происходит только на первой итерации.

func (l *Logger) Output(calldepth int, s string) error {
	...
	l.buf = l.buf[:0]
	l.formatHeader(&l.buf, now, file, line)
	l.buf = append(l.buf, s...)
	if len(s) == 0 || s[len(s)-1] != '\n' {
		l.buf = append(l.buf, '\n')
	}
	_, err := l.out.Write(l.buf)
	return err
}
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы