Как реализовать пул памяти в Golang

я реализовал HTTP-сервер в Go.

для каждого запроса мне нужно создать сотни объектов для определенной структуры, и у меня есть ~10 таких структур. Поэтому после завершения запроса в соответствии с реализацией Go будет собран мусор.

поэтому для каждого запроса будет выделено и освобождено столько памяти.

вместо этого я хотел реализовать пул памяти для повышения производительности со стороны распределения, а также со стороны GC также

в начале запроса я возьму из пула и верну их после того, как запрос будет подан

со стороны реализации пула

  1. как выделить и освободить память определенного типа структуры?
  2. как отслеживать информацию, которую эта память получила, а другая нет?

любые другие предложения по повышению производительности в случае выделения и освобождения памяти?

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
}