Go sync.Pool

от автора

Вольный пересказ документации к sync.Pool

Сборщик мусора (далее GC) не постоянно собирает мусор, а через определённые промежутки времени. В случае если ваш код выделяет память под некоторые структуры данных, а потом освобождает их — и так по кругу — это вызывает определённое давление на GC, в том числе заставляет runtime обратиться к ОС для выделения новой памяти. Представьте: выделяем кусок (например []byte), работаем с ним, освобождаем. Пройдёт определённое время, прежде GC "очнётся ото сна" и соберёт этот кусок. Если в это время мы выделим ещё один такой же кусок и уже выделенной у ОС памяти на это не хватит, то приложение будет вынуждено запросить у ОС ещё памяти. По времени приложения запрос памяти у ОС длится вечность. А в это самое время где-то пылится, ждёт своего часа тот прежний "отработанный" кусок.
Что же делать?

  • создать пул
  • сбрасывать состояние куска
  • складывать в пул отработанные куски
  • брать новые куски из пула

Создать пул

import(     "sync" )  var bytesPool = sync.Pool{     New: func() interface{} { return []byte{} }, }  /* В данном примере функция `New` не нужна. Если пул пуст, и `New` не `nil` - то она будет использована для создания нового объекта. Его нужно будет преобразовать из `interace{}` - привести к нужному типу. Смотри ниже - про это есть децл. */

Сбросить состояние

// пусть ary у нас []byte определённой длины и ёмкости ary = ary[:0] // усекаем len, сохраняем cap

Положить в пул

/* так или иначе у нас могут оказаться слишком большие куски, которые в принципе нам не понадобятся (во всяком случае не часто) - выбросим их; иначе: кусок размером 2048 байт будет использоваться там где нужно всего 500-800 байт, при большом количестве это негативно отразится на памяти - а ведь мы с этим и боремся */ const maxCap = 1024  if cap(ary) <= maxCap {     // кладём в пул куски ограниченного размера     bytesPool.Put(ary) }

Взять из пула

nextAry := bytesPool.Get().([]byte)

Пояснение про New

Функция New создаёт пустой []byte{}, да ещё и эти преобразования в interface{} и обратно. В случае с []byte мы скорее всего будем наращивать его с помощью append, что в принципе делает такой подход не выгодным:

  • создание []byte нулевой ёмкости
  • двойное преобразование в interface{} и обратно
  • append всё равно создаст новый кусок
  • append можно скормить nil, только типа []byte (а не interface{})

Гораздо удобней сделать две функции, которые бы занимались всей вознёй с пулом

// получить func getBytes() (b []byte) {     ifc := bytesPool.Get()     if ifc != nil {         b = ifc.([]byte)     }     return } // положить func putBytes(b []byte) {     if cap(b) <= maxCap {         b = b[:0] // сброс         bytesPool.Put(b)     } }

Помните

  • sync.Pool не панацея
  • пул горутино-безопасен
  • пул не обязательно освободит данные при первом пробуждении GC, но он может освободить их в любой момент
  • нет возможности определить и установить размер пула
  • нет необходимости заботится о переполнении пула
  • вовсе незачем городить пул везде где ни попади, он создавался как амортизатор при множественном совместном использовании некоторых общих объектов, даже не просто внутри пакета, а даже больше — другими пакетами
  • вероятно у Вас есть или будут ситуации, когда необходимость/возможность помочь GC будет очевидной
  • пул ограниченного размера делается с помощью канала с буфером

Хороший пример использования пула: пакет fmt. Со 109-ой по 150-ую строку.


Я

Никто ещё не голосовал. Воздержавшихся нет.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

ссылка на оригинал статьи https://habrahabr.ru/post/277137/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *