Go避免使用大堆造成的高GC开销

原文链接:Avoiding high GC overhead with large heaps

Go的Garbage Collector(GC)在分配的内存量相对较小时工作得非常好,但是如果堆较大,GC最终可能会占用大量的CPU,在极端情况下,它甚至可能无法跟上节奏。

What‘s the problem?

GC的工作就是确定哪些内存可以释放,它是通过扫描内存查找内存分配的指针来完成这个工作的。简而言之,如果对于一个内存分配没有一个指针指向它,则它就可以被释放了。这个工作得非常好,但是内存空间越大扫描需要花费的时间越长。

假设你在开发一个内存数据库,或者你在构建一个需要巨大的查找表的数据流水线。在这些场景下,你可能有数个G的内存分配。在这些情形下,你可能会因为GC损失很多的性能。

Is it a big problem?

让我们来看看这个问题到底有多大?下面通过一段很小的代码来演示这个问题。我们分配了10亿(1e9)个8字节的指针,总共占用了8G的内存。然后我强制执行一次GC,并统计GC花费了多少时间。这个过程我们执行了多次来消除误差获得一个比较稳定的数据。在实例代码中,我们还调用了runtime.KeepAlive()来保证GC或者编译器不会优化掉或回收没有被引用的内存分配。

func main() {
	a := make([]*int, 1e9)

	for i := 0; i < 10; i++ {
		start := time.Now()
		runtime.GC()
		fmt.Printf("GC took %s\n", time.Since(start))
	}

	runtime.KeepAlive(a)
}

这段代码的输出如下:

GC took 4.275752421s
GC took 1.465274593s
GC took 652.591348ms
GC took 648.295749ms
GC took 574.027934ms
GC took 560.615987ms
GC took 555.199337ms
GC took 1.071215002s
GC took 544.226187ms
GC took 545.682881ms

可以看到GC占用的时间基本稳定在半秒左右。这里有10亿个指针耶,这有什么值得惊讶的?每个指针所分摊的时间看起来都低于纳秒,这个对于指针查找已经是一个很不错的速度了。

So what next?

这看起来是一个基本原则的问题。假如我们的应用就是需要一个很大的内存查找表,或者我们的应用基本上就是一个很大的内存查找表,那么我们就会遇到这个问题。如果GC以一个固定的时间周期扫描所有的已经分配的内存,我们将会因为GC损失巨大的CPU可用处理能力。对于这种情况我们能做什么呢?

Make our memory dull

如何让内存不被GC盯上?嗯,GC是在查找指针。如果我们分配的对象的类型不包括指针呢,GC还会扫描它们么?

让我们来试试。下面的示例中,我们分配了与前面示例完全相同的内存,但是现在我们没有包含指针类型在里面。我们分配了一个包含了10亿个int类型的数组,这同样占用了8GB的内存。

func main() {
	a := make([]int, 1e9)

	for i := 0; i < 10; i++ {
		start := time.Now()
		runtime.GC()
		fmt.Printf(&
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值