如何在Golang中实现内存池

时间:2016-07-21 13:37:00

标签: memory-management go

我在Go中实现了一个HTTP服务器。

对于每个请求,我需要为特定的结构创建数百个对象,我有~10个这样的结构。因此,根据Go实现完成请求后,它将被垃圾收集。

因此,对于每个请求,将分配和释放大量内存。

相反,我想实现内存池以提高分配方和GC方的性能

在请求开始时,我将从池中取出并在请求提供后将其放回

从池实施方面

  1. 如何分配和释放特定类型结构的内存?
  2. 如何跟踪这个内存被分配的信息,而其他内存不是?
  3. 在内存分配和释放的情况下提高性能的其他任何建议?

2 个答案:

答案 0 :(得分:23)

事先注意:

许多人建议使用sync.Pool,这是临时对象的快速,良好的实现。但请注意sync.Pool并不保证合并的对象会被保留。引用其文档:

  

存储在游泳池中的任何项目都可能随时自动删除,恕不另行通知。如果Pool在发生这种情况时持有唯一的引用,则该项可能会被释放。

因此,如果您不希望Pool中的对象收集垃圾(这取决于您的情况可能导致更多分配),则下面提供的解决方案会更好,因为频道和#39; s缓冲区不是垃圾回收。如果您的对象真的那么大,那么内存池是合理的,那么池通道的开销将被摊销。

此外,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上尝试。

请注意,此实现会阻止所有&#34;汇集&#34;对象正在使用中。如果您不想这样做,可以使用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尝试从/向池中获取/返回值,则池最初可能为空。

您必须确保不会在请求之间泄露信息,例如确保您不会在已设置且属于其他请求的共享对象中使用字段和值。

答案 1 :(得分:13)

这是@JimB提到的while实现。注意使用sync.Pool将对象返回池。

defer