背景
open-falcon的graph模块占用内存太多, 即发起了graph的内存优化,在上线过程中发生了曲线值异常的问题
过程
1
2
3
4
5
6
7
8
|
func (this *SafeLinkedList) PushFront(item *cmodel.GraphItem) {
- v := smodel.GraphItemPoint{}.New(item)
+ v := smodel.NewPointObjFromPool(item)
+ defer smodel.PutPointObjBack2Pool(v)
...
this.L.PushFront(v)
|
上述代码中, PushFront
是每个点到来都需要操作的函数, 即每上报一个点, smodel.GraphItemPoint{}.New(item)
都需要执行一次
因为这里是一个临时对象,所以尝试使用对象池来优化(虽然后面证明优化无效,但整个过程值得分享),以期望减少内存的分配及申请对象的次数,进而减少gc
注意上面的this.L.PushFront(v)
,这里是一个关键点
另外为对象池增加了helper函数,方便进行对象的Get和Put操作,如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
+var pointObjPool = sync.Pool{New: func() interface{} { return &GraphItemPoint{} }}
+
+func NewPointObjFromPool(item *cmodel.GraphItem) *GraphItemPoint {
+ ret := pointObjPool.Get().(*GraphItemPoint)
+ if item == nil {
+ return nil
+ }
+
+ ret.Timestamp = item.Timestamp
+ ret.Value = item.Value
+
+ return ret
+}
+
+func PutPointObjBack2Pool(p *GraphItemPoint) {
+ pointObjPool.Put(p)
+}
+
|
在这种场景下,使用sync.Pool对象池Get一个新的对象后,使用this.L.PushFront将对象的地址(因为返回的是一个指针),放在了内存cache中。
当执行查询操作时,在内存cache中获取到的是一个地址,将这个地址解引用获取内容时,就会出现问题。
因为这个对象地址是从对象池申请的,该地址在执行Put操作后,对于对象池而言就没什么用了。
而对象池的再次Get操作,很有可能分配出相同的地址。而在实际业务中,这个地址是有意义的,所以造成了从地址解引用实际内容时出现了问题。
整个过程有些类似C里面野指针的概念。
解决
如果从对象池获取值而非指针的话,那么这个问题就解了,如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
func (this *SafeLinkedList) PushFront(item *cmodel.GraphItem) {
if item == nil {
return
}
v := smodel.NewPointObjFromPool()
...
v.Timestamp = item.Timestamp
v.Value = item.Value
...
this.L.PushFront(&v)
smodel.PutPointObjBack2Pool(v)
|
关于对象池的更改,如下
1
2
3
4
5
6
|
func NewPointObjFromPool() GraphItemPoint {
return pointObjPool.Get().(GraphItemPoint)
}
func PutPointObjBack2Pool(p GraphItemPoint) {
pointObjPool.Put(p)
}
|
-EOF-