Как реализовать пул памяти в Golang
я реализовал HTTP-сервер в Go.
для каждого запроса мне нужно создать сотни объектов для определенной структуры, и у меня есть ~10 таких структур. Поэтому после завершения запроса в соответствии с реализацией Go будет собран мусор.
поэтому для каждого запроса будет выделено и освобождено столько памяти.
вместо этого я хотел реализовать пул памяти для повышения производительности со стороны распределения, а также со стороны GC также
в начале запроса я возьму из пула и верну их после того, как запрос будет подан
со стороны реализации пула
- как выделить и освободить память определенного типа структуры?
- как отслеживать информацию, которую эта память получила, а другая нет?
любые другие предложения по повышению производительности в случае выделения и освобождения памяти?
2 ответов
заранее Примечание:
многие предлагают использовать sync.Pool
что является быстрой, хорошей реализацией для временное объекты. Но обратите внимание, что sync.Pool
не гарантирует сохранения Объединенных объектов. Цитата из его документа:
любой элемент, хранящийся в пуле может быть автоматически удален в любое время без уведомления. Если пул содержит только ссылку, когда это происходит, элемент может быть освобожденным.
так что если вы не хотите, чтобы ваши объекты в Pool
чтобы собрать мусор (что в зависимости от вашего случая может привести к большему распределению), решение, представленное ниже, лучше, так как значения в буфере канала не собираются в мусор. Если ваши объекты действительно настолько велики, что пул памяти оправдан, накладные расходы канала пула будут амортизированы.
кроме того, sync.Pool
не позволяет ограничить количество объединенных объекты, в то время как представленное ниже решение, естественно, делает.
простейшая реализация пула памяти-это буферизованный канал.
предположим, вам нужен пул памяти некоторых больших объектов. Создайте буферизованный канал, содержащий указатели на значения таких дорогостоящих объектов, и всякий раз, когда вам это нужно, получите один из пула (канала). Когда вы закончите использовать его, положите его обратно в пул (отправить по каналу). Чтобы избежать случайной потери объектов (например, в случай паники), используйте defer
заявление при их возврате.
давайте использовать это как тип наших больших объектов:
type BigObject struct {
Id int
Something string
}
создание пула составляет:
pool := make(chan *BigObject, 10)
размер пула-это просто размер буфера канала.
заполнение пула указателями дорогих объектов (это необязательно, см. Примечания в конце):
for i := 0; i < cap(pool); i++ {
bo := &BigObject{Id: i}
pool <- bo
}
использование пула многими goroutines:
wg := sync.WaitGroup{}
for i := 0; i < 100; i++ {
wg.Add(1)
go func() {
defer wg.Done()
bo := <-pool
defer func() { pool <- bo }()
fmt.Println("Using", bo.Id)
fmt.Println("Releasing", bo.Id)
}()
}
wg.Wait()
попробуйте на Go Playground.
обратите внимание, что эта реализация блокирует, если все" объединенные " объекты используются. Если вы не хотите этого, вы можете использовать select
чтобы заставить создавать новые объекты, если все они используются:
var bo *BigObject
select {
case bo = <-pool: // Try to get one from the pool
default: // All in use, create a new, temporary:
bo = &BigObject{Id:-1}
}
и в этом случае вам не нужно класть его обратно в бассейн. Или вы можете попытаться вернуть все обратно в бассейн, если в бассейне есть место, без блокировки, снова с select
:
select {
case pool <- bo: // Try to put back into the pool
default: // Pool is full, will be garbage collected
}
Примечания:
заполнять бассеин предыдущий опционный. Если вы используете select
чтобы попытаться получить / вернуть значения из / в пул, пул может изначально быть пустым.
вы должны убедиться, что вы не утечка информации между запросами, например, убедитесь, что вы не используете поля и значения в общей объектов, которые были установлены и принадлежат на другие запросы.
Это sync.Pool
реализация, упомянутая @JimB. Виду использование defer
для возврата объекта в пул.
package main
import "sync"
type Something struct {
Name string
}
var pool = sync.Pool{
New: func() interface{} {
return &Something{}
},
}
func main() {
s := pool.Get().(*Something)
defer pool.Put(s)
s.Name = "hello"
// use the object
}