博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
GC优化
阅读量:5886 次
发布时间:2019-06-19

本文共 2741 字,大约阅读时间需要 9 分钟。

hot3.png

GC优化:比如erlang内核源码,erlang虚拟机通过申请大块内存,然后自己红黑树管理空闲的块,避免了syscall linux内核,频繁小片内存申请影响性能,并且容易产生碎片

下面看看国外大牛go的GC优化心得:

几周前我们分享了一个帖子讲述我们为什么选择Go语言编写CockroachDB,我们收到一些问题,询问我们是如何解决Go语言的一些已知问题,特别是关于性能、GC和死锁的问题。

本文中我们将分享几个非常有用的优化技巧用以改善许多常见的GC性能问题(接下来还将覆盖一些有趣的死锁问题)。我们将重点分享如何通过嵌套结构体、使用 sync.Pool、和复用后端数组减少内存分配和降低GC开销。

减少内存分配和GC优化

将Go与其他语言(比如)区别开来的是Go语言能让你管理内存布局。通过GO语言,你可以合并碎片,而其他垃圾集合语言不能。

让我们看看CockroachDB中从磁盘读取数据并解码的一小段代码:

metaKey := mvccEncodeMetaKey(key)var meta MVCCMetadataif err := db.GetProto(metaKey, &meta); err != nil {    // Handle err}...valueKey := makeEncodeValueKey(meta)var value MVCCValueif err := db.GetProto(valueKey, &value); err != nil {    // Handle err}

为了读取数据,我们执行了4次内存分配:MVCCMetadata结构体、MVCCValue结构体和metaKey、valueKey。在Go语言中我们可以通过合并结构体和预分配空间给Key把内存分配减少为1次。

type getBuffer struct {    meta MVCCMetadata    value MVCCValue    key [1024]byte}var buf getBuffermetaKey := mvccEncodeKey(buf.key[:0], key)if err := db.GetProto(metaKey, &buf.meta); err != nil {    // Handle err}...valueKey := makeEncodeValueKey(buf.key[:0], meta)if err := db.GetProto(valueKey, &buf.value); err != nil {    // Handle err}

我们了一个getBuffer类型,包含两个不同的结构体:MVCCMetadata和MVCCValue(都是protobuf对象),不同于通常使用的切片,第三个成员使用了一个数组。

不需要额外分配内存,你就可以直接在结构体中定义一个定长的数组(1024 bytes),这允许我们将三个对象放到同一个getBuffer结构体中。这样我们就把4次内存分配减少为1次。需要注意的的两个不同的key我们使用了同一个数组,在两个key不同时使用的情况下是可以正常工作的。稍后我们再来讨论数组。(就是复用meta和value,但是key不同的方式来减少内存分配)

sync.Pool

var getBufferPool = sync.Pool{       New: func () interface{} {              return &getBuffer{}       },}

说实话,我们花了一段时间才弄明白为什么 sync.Pool 才是我们我们想要的。在一个GC周期内可以无限制使用同一个对象无需多次内存分配,GC会负责回收。在每次GC启动的时候都会清除Pool中的对象。

用一个例子来说明如何使用 sync.Pool:

buf := getBufferPool.Get().(*getBuffer)defer getBufferPoolPut(buf)key := append(but.key[0:0], ...)

首先你需要使用一个工厂来一个的 sync.Pool 对象,在这个列子中我们分配一个 getBuffer结构体并返回。我们不再创建新的 getBuffer 改为从 pool 中获取。Pool.Get 返回的是一个空接口,我们需要使用类型转换。使用完成后再放回到 pool 中。最终的结果是我们无需每次获取 getBuffer时都分配一次内存。(用pool的话一定有锁/sync争用的代价)

数组和切片

有些事可能不值一提,在Go语言中数组和切片是不同的类型,而且切片和数组几乎所有操作都一样。你仅仅通过一个方括号 [:0] 就可以从数组得到一个切片。

key := append(bf.key[0:0], ...)

这里使用数组创建了一个长度为0的切片。事实是这个切片已经拥有了一个后端存储,意思是说对切片的append操作实际上插入到数组中,而并没有分配新的内存。所以当我们解码一个key时,我们可以append进一个通过这个 buffer 创建的切片中。只要key的长度小于 1 KB,我们就不需要做任何内存分配。将复用我们给数组分配的内存。

key 的长度超过 1 KB 的情况可能会有但是不常见,在这种情况下,程序可以的自动分配新的后端数组,我们的代码不需要做任何处理。(这个方式不错)

Gogoprotobuf vs Google protobuf

最后,我们在磁盘上所有的数据都使用了protobuf。然而我们并没有使用 Google官方的protobuf类库,我们强烈推荐使用一个叫做 gogoprotobuf的分支。

Gogoprotobuf 遵循了很多我们上面提到的关于避免不必要的内存分配的原则。尤其是,它允许将数据编码到一个后端使用数组的字节切片以避免多次内存分配。此外,非空注解允许你直接嵌入消息而无需额外的内存分配开销,这在始终需要嵌入消息时是非常有用的。

最后一点优化是,较基于反射进行编码和解编码的Google标准protobuf类库,gogoprotobuf使用编码和解编码协程提供了不错的性能改善。

总结

通过结合上述技巧,我们已经可以最小化GC的性能开销和优化更好的性能。当我们接近测试阶段,更多地专注于内存分析,我们将在后续的帖子中分享我们的成果。当然,如果你知道其他的Go语言性能优化,我们洗耳恭听。

转载于:https://my.oschina.net/yang1992/blog/540064

你可能感兴趣的文章
Android 设计模式一:EIT造型
查看>>
单机搭建Android开发环境(二)
查看>>
如何申请iOS开发者证书/发布app到手机
查看>>
String、StringBuffer、StringBuilder的区别
查看>>
android实现gif动态图的使用
查看>>
12.5. 集成 Mybatis
查看>>
如何去掉NavigationBar的backButton
查看>>
Android github 快速实现多人协作
查看>>
SAP S/4 HANA新变化-SD销售与分销
查看>>
winform 窗体实现增删改查(CRUD)窗体基类模式
查看>>
MySQL · 特性分析 · 数据一样checksum不一样
查看>>
CIO的责任不仅仅是信息管理
查看>>
vs2015 Xamarin.Android安装
查看>>
SpringMVC空字符串转为null
查看>>
我需要完全理解这部分代码才能确保它能够正常工作,如果由我来修复代码中的问题,我是不会这么写的,因此希望你也不要这么来写(转)...
查看>>
Spark_SQl
查看>>
查看光纤卡的WWN号
查看>>
【百度地图API】如何在地图上添加标注?——另有:坐标拾取工具+打车费用接口介绍...
查看>>
机器学习之梯度下降法
查看>>
笔记本外接2-3个屏幕
查看>>