golang 经验交流, 如何避免 gc 杀手

2015-12-09 10:40:22 +08:00
 dbow

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())
}

大家还有什么好注意? 欢迎交流。

8836 次点击
所在节点    程序员
34 条回复
pathletboy
2015-12-09 11:40:30 +08:00
golang 标准库 1.4 起自带内存池
https://golang.org/pkg/sync/#Pool
tiancaiamao
2015-12-09 12:10:04 +08:00
你为什么不用 bytes.Buffer 要自己写呢?标准库里面的 http://godoc.org/bytes

不是 golang 的 gc 弱爆了,是你对 golang 的了解弱爆了
mengzhuo
2015-12-09 12:23:56 +08:00
sync.Pool
bytes.Buffer
<- chan []byte (LeakyBuffer)

官方都快有 3 种标准写法了, LZ 你弱爆了
Comdex
2015-12-09 12:39:57 +08:00
看了楼上的回复我感觉自己弱爆了。。。
xufang
2015-12-09 12:42:01 +08:00
helloworldwt
2015-12-09 13:13:44 +08:00
nice
miao1007
2015-12-09 13:16:52 +08:00
golang 是个大坑啊,比 shell 还难写
mengzhuo
2015-12-09 13:22:45 +08:00
@miao1007
这两都不是一类
说难写的看看 Erlang CommonLisp
yuankui
2015-12-09 14:47:16 +08:00
嗯,是实践,而不是最佳实践~
amaranthf
2015-12-09 14:59:28 +08:00
@mengzhuo lisp 大部分时候写起来还是蛮舒服的,除了设计宏的时候
gamexg
2015-12-09 15:37:57 +08:00
erlang 啃了一段时间,最后实在习惯不了那种思路,放弃。
dbow
2015-12-09 17:26:40 +08:00
@pathletboy
@mengzhuo
@tiancaiamao
我其实是想讨论下怎么把内存释放掉, 内存池只是避免通过 gc 的方案
c 里有 malloc , free 成对, 成对使用就可以保持内存消耗相对较小
golang 里就不一样, 一路 malloc, 什么时候 free 是不确定事件, 最后内存使用越来越大, 不可控制。
pathletboy
2015-12-09 17:29:40 +08:00
@dbow 关键你本来就不该频繁 malloc free 啊,就算你 c ,这么频繁 malloc free ,增加内存碎片,影响性能,最终还是要靠内存池来解决。
pathletboy
2015-12-09 17:35:34 +08:00
@dbow 如果你非得坚持错误的做法,那你可以手工 gc , runtime.GC(),不推荐!不推荐!不推荐!
dbow
2015-12-09 17:50:52 +08:00
@pathletboy
性能不是关键问题, 随着 objects 越来越多, 进程内存越占越多, gc 的回收效果不好才是个大麻烦。
dbow
2015-12-09 17:58:07 +08:00
@dbow
while {readline} 不建内存池这种模式下, 10 亿行文本, 吃内存嗖嗖的, 慢点是没有关系的.
cloudzhou
2015-12-09 19:18:36 +08:00
-- while {readline} 不建内存池这种模式下, 10 亿行文本, 吃内存嗖嗖的, 慢点是没有关系的.

我并不理解你这个需求,如果你能提供更多代码就更好了。因为在 while 里面,最终只是复用一个 slice 而已(当然在超过当前长度的时候会申请新的内存空间), GC 应该是可控的。
// 使用 *bufio.Reader ReadLine

上面提到的几种方案, sync.Pool , bytes.Buffer ,有什么理由不能用吗?
mengzhuo
2015-12-09 21:41:02 +08:00
@dbow
本来 GC 就是为了减轻心智负担的,非得自己整,不是作死是啥,还怪语言?
觉得不“可控”,你去写 C 好了,多进程下检测泄露、死锁?
连 cache tmpfile 都不会用,还 10 亿行文本,呵呵。
xufang
2015-12-09 21:42:04 +08:00
@mengzhuo 和小学生解释这么多干嘛。。。
dbow
2015-12-09 22:17:55 +08:00
停喷, 结贴。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/242195

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX