介绍
一、
临时对象池(pool
)的设计目的是用来保存和复用临时对象,以减少内存分配,降低CG压力; 同时, 利用对象还实现了临时对象的复用, 从而降低某些场景下重复申请内存所消耗的时间。
想感受下对象池的威力的话, 就一起看一个简单的例子吧:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| package main
import ( "fmt" "sync" "time" )
type structR6 struct { B1 [100000]int }
var r6Pool = sync.Pool{ New: func() interface{} { return new(structR6) }, }
func usePool() { startTime := time.Now() for i := 0; i < 10000; i++ { sr6 := r6Pool.Get().(*structR6) sr6.B1[0] = 0 sr6.B1[1] = 5 r6Pool.Put(sr6) } fmt.Println("pool Used:", time.Since(startTime).Microseconds()) }
func standard() { startTime := time.Now() for i := 0; i < 10000; i++ { var sr6 *structR6 = new(structR6) sr6.B1[0] = 0 sr6.B1[1] = 5 } fmt.Println("standard Used:", time.Since(startTime).Microseconds()) } func main() { standard() usePool() }
|
执行结果如下图:
一个含有100000个int
值的结构体,在标准方法中,每次均新建,重复10000次,一共需要耗费2449615us;
如果用完的struct
可以废物利用,放回pool
中。需要新的结构体的时候,尝试去pool
中取,而不是重新生成,重复10000次需要的时间几乎可以忽略(可以增加本案例中 自定义结构体成员int数组的大小, 来做更细致的验证)。
也就是说, 大大节省了 GC 和 新建 对象的 资源消耗。 但是对于一个占用内存本就不是很大的对象/结构体
来说, 复用的意义就不是很大了, 这时使用sync.Pool甚至可能增加资源消耗。
您可以通过对 结构体中 int数组 B1 的大小做调整, 从而进一步验证此现象。 或者 移步 这篇文章-> 对于大批量文件的解析操作优化_go 语言案例, 观看此现象
二、
注意, 我们 的复用对象, 一定得是可以复用的
我们看一个简单的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| package main
import ( "fmt" "sync" "time" )
type structR6 struct { B1 [100000]int }
var r6Pool = sync.Pool{ New: func() interface{} { return new(structR6) }, }
func usePool() { startTime := time.Now() cont := 0 for i := 0; i < 10000; i++ { sr6 := r6Pool.Get().(*structR6) sr6.B1[0] = 0 sr6.B1[1] = 5 r6Pool.Put(sr6) } for i := 0; i < 10000; i++ { sr6 := r6Pool.Get().(*structR6) sr6.B1[0] = 1 if(sr6.B1[1] == 5){ cont += 1 } r6Pool.Put(sr6) } fmt.Printf("pool cont = %d 次\n", cont) fmt.Println("pool Used:", time.Since(startTime).Microseconds()) }
func standard() { startTime := time.Now() cont := 0 for i := 0; i < 10000; i++ { var sr6 *structR6 = new(structR6) sr6.B1[0] = 0 sr6.B1[1] = 5 } for i := 0; i < 10000; i++ { var sr6 *structR6 = new(structR6) sr6.B1[0] = 1 if(sr6.B1[1] == 5){ cont += 1 } } fmt.Printf("standard cont = %d 次\n", cont) fmt.Println("standard Used:", time.Since(startTime).Microseconds()) }
func main() { standard() usePool() }
|
执行结果如下图:
我们可以看到, 被加入池中复用的数组, 被取出来时, 还是有可能和原来一个样子的, 并没有对值做重置操作。 因此, 如果您的对象, 在业务上并不是能够复用的, 那么最好在放入sync.Pool前做些什么, 不然不建议使用。
对于对象中的切片, 如果您想要复用其底层数组的话, 需要使用 [:0]来对其进行重置, 当然如果您想放弃底层数组的复用, 让它被GC干掉, 你可以使用 nil来重置数组。
三、
如果您的复用对象非常重要, 建议你手动管理此对象的创建和释放时机, 而不是来选择sync.Pool。 因为, sync.Pool 中对象的回收是依赖与 GC 的, 最多也就比其他对象多活两轮GC, 所以很可能被定期的GC给干掉。
基于之前的例子, 我们几乎都是在复用最初创建的第一个 new(structR6)
, 也就是只开辟了一次空间, 然后一直复用。 由于时间很短, 在进入验证循环前, 有可能并没有遇到过GC, 或者只遇到过1次GC。
因此, 我们 在进入验证循环前, 进行手动 GC , 来模拟出 sync.Poll 内对象的自动回收的必要条件。
下面是测试代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| package main
import ( "runtime" "fmt" "sync" "time" )
type structR6 struct { B1 [100000]int }
var r6Pool = sync.Pool{ New: func() interface{} { return new(structR6) }, }
func usePool() { startTime := time.Now() cont := 0 for i := 0; i < 10000; i++ { sr6 := r6Pool.Get().(*structR6) sr6.B1[0] = 0 sr6.B1[1] = 5 r6Pool.Put(sr6) } runtime.GC() for i := 0; i < 10000; i++ { sr6 := r6Pool.Get().(*structR6) sr6.B1[0] = 1 if(sr6.B1[1] == 5){ cont += 1 } r6Pool.Put(sr6) } fmt.Printf("pool cont = %d 次\n", cont) fmt.Println("pool Used:", time.Since(startTime).Microseconds()) }
func main() { usePool() }
|
执行结果:
可以发现, 当只手动执行一次GC时, 是有可能还会出现 10000 的情况的; 但若手动执行两次GC, 则对象被回收掉就是板上钉钉的事了(因为源码中的缓存机制就是最多两次GC的周期)
剖析
源码剖析, 之后有空了再写。