golang 的 gc 从实践来看弱爆了, while { readline, } 处理大文本很快内存消耗完, 大量 cpu 时间消耗到 gc 上。 下面两个工具可以缓解这种问题, 一是一块 buffer 上用多个 slice 连续存储, 另一个是自动缩放的一块 buffer 存变长的值。
type BytesPool struct {
Buffer []byte
Index int
Top int
}
//size, initial pool size
func NewBytesPool(size int) *BytesPool {
return &BytesPool{make([]byte, size), 0, 0}
}
//get slice
func (b *BytesPool) AllocSlice(size int) []byte {
//expand 1.5x
if len(b.Buffer) < size + b.Index {
nb := make([]byte, int(float64(len(b.Buffer)) * 1.5) + size)
copy(nb, b.Buffer)
b.Buffer = nb
}
slice := b.Buffer[b.Index:b.Index+size]
b.Top = b.Index
b.Index += size
return slice
}
func (b *BytesPool) GetSlice() []byte {
return b.Buffer[b.Top:b.Index]
}
type AutoResizeByteArray struct {
Buffer []byte
Top int
}
func NewAutoResizeByteArray(size int) *AutoResizeByteArray {
return &AutoResizeByteArray{make([]byte, size), 0}
}
func (b *AutoResizeByteArray) AllocSlice(size int) []byte {
if len(b.Buffer) < size {
nb := make([]byte, int(float64(len(b.Buffer)) * 1.5) + size)
copy(nb, b.Buffer)
b.Buffer = nb
}
b.Top = size
return b.Buffer[:size]
}
func (b *AutoResizeByteArray) GetSlice() []byte {
return b.Buffer[:b.Top]
}
func testBytesPool() {
b := NewBytesPool(3)
copy(b.AllocSlice(4), []byte("abcd"))
fmt.Printf("%s\n", b.GetSlice())
copy(b.AllocSlice(2), []byte("ef"))
fmt.Printf("%s\n", b.GetSlice())
b1 := NewAutoResizeByteArray(3)
copy(b1.AllocSlice(4), []byte("abcd"))
fmt.Printf("%s\n", b1.GetSlice())
}
大家还有什么好注意? 欢迎交流。
1
pathletboy 2015-12-09 11:40:30 +08:00
golang 标准库 1.4 起自带内存池
https://golang.org/pkg/sync/#Pool |
2
tiancaiamao 2015-12-09 12:10:04 +08:00
|
3
mengzhuo 2015-12-09 12:23:56 +08:00
sync.Pool
bytes.Buffer <- chan []byte (LeakyBuffer) 官方都快有 3 种标准写法了, LZ 你弱爆了 |
4
Comdex 2015-12-09 12:39:57 +08:00
看了楼上的回复我感觉自己弱爆了。。。
|
5
xufang 2015-12-09 12:42:01 +08:00
|
6
helloworldwt 2015-12-09 13:13:44 +08:00
nice
|
7
miao1007 2015-12-09 13:16:52 +08:00
golang 是个大坑啊,比 shell 还难写
|
9
yuankui 2015-12-09 14:47:16 +08:00
嗯,是实践,而不是最佳实践~
|
11
gamexg 2015-12-09 15:37:57 +08:00
erlang 啃了一段时间,最后实在习惯不了那种思路,放弃。
|
12
dbow OP @pathletboy
@mengzhuo @tiancaiamao 我其实是想讨论下怎么把内存释放掉, 内存池只是避免通过 gc 的方案 c 里有 malloc , free 成对, 成对使用就可以保持内存消耗相对较小 golang 里就不一样, 一路 malloc, 什么时候 free 是不确定事件, 最后内存使用越来越大, 不可控制。 |
13
pathletboy 2015-12-09 17:29:40 +08:00
@dbow 关键你本来就不该频繁 malloc free 啊,就算你 c ,这么频繁 malloc free ,增加内存碎片,影响性能,最终还是要靠内存池来解决。
|
14
pathletboy 2015-12-09 17:35:34 +08:00
@dbow 如果你非得坚持错误的做法,那你可以手工 gc , runtime.GC(),不推荐!不推荐!不推荐!
|
15
dbow OP @pathletboy
性能不是关键问题, 随着 objects 越来越多, 进程内存越占越多, gc 的回收效果不好才是个大麻烦。 |
17
cloudzhou 2015-12-09 19:18:36 +08:00
-- while {readline} 不建内存池这种模式下, 10 亿行文本, 吃内存嗖嗖的, 慢点是没有关系的.
我并不理解你这个需求,如果你能提供更多代码就更好了。因为在 while 里面,最终只是复用一个 slice 而已(当然在超过当前长度的时候会申请新的内存空间), GC 应该是可控的。 // 使用 *bufio.Reader ReadLine 上面提到的几种方案, sync.Pool , bytes.Buffer ,有什么理由不能用吗? |
18
mengzhuo 2015-12-09 21:41:02 +08:00
@dbow
本来 GC 就是为了减轻心智负担的,非得自己整,不是作死是啥,还怪语言? 觉得不“可控”,你去写 C 好了,多进程下检测泄露、死锁? 连 cache tmpfile 都不会用,还 10 亿行文本,呵呵。 |
20
dbow OP 停喷, 结贴。
|
22
dbow OP @cloudzhou
while { readline, 每次都分配内存而且不归我管的函数}, 我讲的是 gc 对这种情况反应不行, 也就是下面的情况做不到. enter function malloc -> new 自动 free -> delete leave function ``` ```go package main func main() { b := make([][]byte, 3000000) for i := 0; i < 3000000; i++ { buffer := make([]byte, 1024) copy(buffer, []byte("abcd")) b[i] = buffer } } ``` ```shell gc1(1): 1+0+33224+1 us, 0 -> 68 MB, 21 (21-0) objects, 2 goroutines, 16/0/0 sweeps, 0(0) handoff, 0(0) steal, 0/0/0 yields gc2(1): 0+2+29460+1 us, 68 -> 68 MB, 22 (23-1) objects, 3 goroutines, 16/0/2 sweeps, 0(0) handoff, 0(0) steal, 0/0/0 yields gc3(1): 0+8+29552+0 us, 68 -> 137 MB, 104710 (139608-34898) objects, 3 goroutines, 8808/0/0 sweeps, 0(0) handoff, 0(0) steal, 0/0/0 yields gc4(1): 0+881+29664+1 us, 136 -> 136 MB, 69814 (139608-69794) objects, 3 goroutines, 8808/0/8803 sweeps, 0(0) handoff, 0(0) steal, 0/0/0 yields gc5(1): 0+10+30403+0 us, 136 -> 273 MB, 278374 (417688-139314) objects, 3 goroutines, 26255/0/0 sweeps, 0(0) handoff, 0(0) steal, 0/0/0 yields gc6(1): 0+2650+30961+0 us, 272 -> 272 MB, 208854 (417688-208834) objects, 3 goroutines, 26255/0/26250 sweeps, 0(0) handoff, 0(0) steal, 0/0/0 yields gc7(1): 0+15+31651+0 us, 272 -> 545 MB, 624366 (971704-347338) objects, 3 goroutines, 61016/0/0 sweeps, 0(0) handoff, 0(0) steal, 0/0/0 yields gc8(1): 0+5795+32788+0 us, 543 -> 543 MB, 485862 (971704-485842) objects, 3 goroutines, 61016/0/61011 sweeps, 0(0) handoff, 0(0) steal, 0/0/0 yields gc9(1): 0+23+33831+0 us, 543 -> 1086 MB, 1313658 (2075432-761774) objects, 3 goroutines, 130267/0/0 sweeps, 0(0) handoff, 0(0) steal, 0/0/0 yields gc10(1): 0+12912+35999+0 us, 1082 -> 1082 MB, 1037726 (2075432-1037706) objects, 3 goroutines, 130267/0/130262 sweeps, 0(0) handoff, 0(0) steal, 0/0/0 yields gc11(1): 1+39+39331+1 us, 1082 -> 2164 MB, 2686898 (4274328-1587430) objects, 3 goroutines, 268233/0/0 sweeps, 0(0) handoff, 0(0) steal, 0/0/0 yields gc12(1): 0+28420+41711+0 us, 2155 -> 2155 MB, 2137174 (4274328-2137154) objects, 3 goroutines, 268233/0/268228 sweeps, 0(0) handoff, 0(0) steal, 0/0/0 yields ``` |
23
dbow OP @cloudzhou 除了做一些底层的 dirty hack, 针对各种 golang 的内置对象, 你有什么办法用 golang 的 api 接口实现这个吗?
```golang enter function malloc -> new 自动 free -> delete leave function ``` |
24
cloudzhou 2015-12-10 12:28:50 +08:00
```go
package main func main() { b := make([][]byte, 3000000) for i := 0; i < 3000000; i++ { buffer := make([]byte, 1024) copy(buffer, []byte("abcd")) b[i] = buffer } } ``` 你这个例子来说几乎是无解的,因为无论如何变量会被引用到,所以 GC 本身不会回收的,哪怕使用 sync.Pool ,这种情况下和 GC 关系不大,哪怕你使用 Java ,一样遇到这个问题。 ** 所以你的问题是,如何能够更加“紧凑”的使用内存,避免内存碎片。** 按照你的这个例子,那么就是大量的循环里面创造 slice ,但是 slice 很大,而实际存储内容比较小。 “看起来内存很浪费” 解决方法来看: 1 如果使用自己开发的内存池,在大量动态变化情况下,实际上,你就是在实现一个小型的 GC 了。并且不会比使用 sync.Pool 好多少的。 2 借鉴 memcache 的解决方法,申请大的内存块,然后按照长度切片,比如 128b, 256b, 512b, 1k, 2k ,然后根据实际数据做一些 copy 工作。 3 sync.Pool 和 按照长度分片的 buffer 结合起来,基本能实现你的需求了。 节省内存和避免 COPY 是一个矛盾的问题,内存越紧凑,当长度变化时,需要申请新的空间, COPY 数据,反之就是内存越浪费,这是一个权衡的问题。 |
25
dbow OP @cloudzhou
如果能实现下面这种模式, 问题也是可以解决的, while {malloc, free}, 这样内存耗用就相对较小, 不会 malloc 堆在一起又占内存而且一下 gc 造成巨大的延迟。 ```gol enter function malloc -> new 自动 free -> delete leave function ``` |
26
tiancaiamao 2015-12-10 12:38:34 +08:00
@dbow
什么叫 dirty....自己去操心资源的分配和释放这些事情就很 dirty 什么叫 hack... 比如 c 语言经常这么写: void f(int *multiret1, int *multiret1); // 多值返回 void f(struct T* bufFromCallerStack); // 在调用者的栈空间中分配 struct T { struct ListNode node; // 从 node 的指针“反射”取出 T 对象 } ------不熟习 c 的人说,这叫 hack-------- ------熟习 c 的人说,这是常识啊----- 对象池,复用缓存空间,这些对于熟习 Go 的人说是常识,不是 hack |
27
dbow OP @tiancaiamao
C 语言指针可以乱跑就没 hack 这回事, 我讲的是 go 里要实现如下模式, 需要 hack enter function malloc -> new 自动 free -> delete leave function |
28
tiancaiamao 2015-12-10 12:50:25 +08:00
又没人阻止你这么做
func f(reuse []byte) { enter function use reuse object ,不要 malloc 不需要 free leave function } |
29
dbow OP @tiancaiamao
问题是 f 多数情况下不是自己写的, 所以要求 gc 能自己搞定这个事。 |
30
yernsun 2015-12-10 14:14:49 +08:00
可能我比较 low ,申请资源的既然能进行 free -> delete ,那么为什么就不能复用?
|
31
xufang 2015-12-10 23:02:34 +08:00
唉,我在 5# 贴得链接无人问津,明珠暗投啊。
|
32
ryd994 2015-12-11 05:37:49 +08:00
|