• 原文地址:medium.com/@blanchon.v…
  • 原文作者:Vincent Blanchon
  • 译文地址:github.com/watermelo/d…
  • 译者:咔叽咔叽
  • 译者水平有限,如有翻译或理解谬误,烦请帮忙指出

ℹ️本文基于 Go 1.12 和 1.13 版本,并解释了这两个版本之间 sync/pool.go 的演变。

sync 包提供了一个强大且可复用的实例池,以减少 GC 压力。在使用该包之前,我们需要在使用池之前和之后对应用程序进行基准测试。这非常重要,因为如果不了解它内部的工作原理,可能会影响性能。

池的限制

我们来看一个例子以了解它如何在一个非常简单的上下文中分配 10k 次:

type Small struct {a int
}var pool = sync.Pool{New: func() interface{} { return new(Small) },
}//go:noinline
func inc(s *Small) { s.a++ }func BenchmarkWithoutPool(b *testing.B) {var s *Smallfor i := 0; i < b.N; i++ {for j := 0; j < 10000; j++ {s = &Small{ a: 1, }b.StopTimer(); inc(s); b.StartTimer()}}
}func BenchmarkWithPool(b *testing.B) {var s *Smallfor i := 0; i < b.N; i++ {for j := 0; j < 10000; j++ {s = pool.Get().(*Small)s.a = 1b.StopTimer(); inc(s); b.StartTimer()pool.Put(s)}}
}
复制代码

上面有两个基准测试,一个没有使用 sync.Pool,另一个使用了:

name           time/op        alloc/op        allocs/op
WithoutPool-8  3.02ms ± 1%    160kB ± 0%      1.05kB ± 1%
WithPool-8     1.36ms ± 6%   1.05kB ± 0%        3.00 ± 0%
复制代码

由于循环有 10k 次迭代,因此不使用池的基准测试在堆上需要 10k 次内存分配,而使用了池的基准测试仅进行了 3 次分配。 这 3 次分配由池产生的,但却只分配了一个结构实例。目前看起来还不错;使用 sync.Pool 更快,消耗更少的内存。

但是,在一个真实的应用程序中,你的实例可能会被用于处理繁重的任务,并会做很多头部内存分配。在这种情况下,当内存增加时,将会触发 GC。我们还可以使用命令 runtime.GC() 来强制执行基准测试中的 GC 来模拟此行为:(译者注:在 Benchmark 的每次迭代中添加runtime.GC()

name           time/op        alloc/op        allocs/op
WithoutPool-8  993ms ± 1%    249kB ± 2%      10.9k ± 0%
WithPool-8     1.03s ± 4%    10.6MB ± 0%     31.0k ± 0%
复制代码

我们现在可以看到,在 GC 的情况下池的性能较低,分配数和内存使用也更高。我们继续更深入地了解原因。

池的内部工作流程

深入了解 sync/pool.go 包的初始化,可以帮助我们之前的问题的答案:

func init() {runtime_registerPoolCleanup(poolCleanup)
}
复制代码

他将注册一个在运行时清理 pool 对象的方法。GC 在文件 runtime/mgc.go 中将触发这个方法:

func gcStart(trigger gcTrigger) {[...]// 在开始 GC 前调用 clearpoolsclearpools()
复制代码

这就解释了为什么在调用 GC 时性能较低。因为每次 GC 运行时都会清理 pool 对象(译者注:pool 对象的生存时间介于两次 GC 之间)。文档也告知我们:

存储在池中的任何内容都可以在不被通知的情况下随时自动删除

现在,让我们创建一个流程图以了解池的管理方式:

对于我们创建的每个 sync.Pool,go 生成一个连接到每个处理器(译者注:处理器即 Go 中调度模型 GMP 的 P,pool 里实际存储形式是 [P]poolLocal)的内部池 poolLocal。该结构由两个属性组成:privateshared。第一个只能由其所有者访问(push 和 pop 不需要任何锁),而 shared 属性可由任何其他处理器读取,并且需要并发安全。实际上,池不是简单的本地缓存,它可以被我们的应用程序中的任何 线程/goroutines 使用。

Go 的 1.13 版本将改进 shared 的访问,并且还将带来一个新的缓存,以解决 GC 和池清理相关的问题。

新的无锁池和 victim 缓存

Go 1.13 版将 shared 用一个双向链表poolChain作为储存结构,这次改动删除了锁并改善了 shared 的访问。以下是 shared 访问的新流程:

使用这个新的链式结构池,每个处理器可以在其 shared 队列的头部 push 和 pop,而其他处理器访问 shared 只能从尾部 pop。由于 next/prev 属性,shared 队列的头部可以通过分配一个两倍大的新结构来扩容,该结构将链接到前一个结构。初始结构的默认大小为 8。这意味着第二个结构将是 16,第三个结构 32,依此类推。

此外,现在 poolLocal 结构不需要锁了,代码可以依赖于原子操作。

关于新加的 victim 缓存(译者注:关于引入 victim 缓存的 commit,引入该缓存就是为了解决之前 Benchmark 那个问题),新策略非常简单。现在有两组池:活动池和存档池(译者注:allPoolsoldPools)。当 GC 运行时,它会将每个池的引用保存到池中的新属性(victim),然后在清理当前池之前将该组池变成存档池:

// 从所有 pool 中删除 victim 缓存
for _, p := range oldPools {p.victim = nilp.victimSize = 0
}// 把主缓存移到 victim 缓存
for _, p := range allPools {p.victim = p.localp.victimSize = p.localSizep.local = nilp.localSize = 0
}// 非空主缓存的池现在具有非空的 victim 缓存,并且池的主缓存被清除
oldPools, allPools = allPools, nil
复制代码

有了这个策略,应用程序现在将有一个循环的 GC 来 创建/收集 具有备份的新元素,这要归功于 victim 缓存。在之前的流程图中,将在请求"shared" pool 的流程之后请求 victim 缓存。

转载于:https://juejin.im/post/5d006254e51d45776031afe3

[译] Go: 理解 Sync.Pool 的设计相关推荐

  1. 深度解密Go语言之sync.pool

    最近在工作中碰到了 GC 的问题:项目中大量重复地创建许多对象,造成 GC 的工作量巨大,CPU 频繁掉底.准备使用 sync.Pool 来缓存对象,减轻 GC 的消耗.为了用起来更顺畅,我特地研究了 ...

  2. 手摸手Go 深入剖析sync.Pool

    作者 | Leo叔叔       责编 | 欧阳姝黎 如果能够将所有内存都分配到栈上无疑性能是最佳的,但不幸的是我们不可避免需要使用堆上分配的内存.我们可以优化使用堆内存时的性能损耗吗?答案是肯定的. ...

  3. golang的临时对象池sync.Pool

    今天在写码之时,发现了同事用到了sync.pool.因不知其因,遂Google之.虽然大概知道其原因和用法.还不能融汇贯通.故写此记,方便日后查阅.直至明了. 正文 在高并发或者大量的数据请求的场景中 ...

  4. (译)理解 LSTM 网络 (Understanding LSTM Networks by colah)

    前言:其实之前就已经用过 LSTM 了,是在深度学习框架 keras 上直接用的,但是到现在对LSTM详细的网络结构还是不了解,心里牵挂着难受呀!今天看了 tensorflow 文档上面推荐的这篇博文 ...

  5. Go的sync.Pool(五)

    Pool 作用 sync.Pool的作用是存储那些被分配了但是没有被使用,而未来可能会使用的值,以减小垃圾回收的压力,Pool不太适合做永久保存的池,更适合做临时对象池.在Go语言的程序设计中,这是为 ...

  6. [转载]golang sync.Pool

    2019独角兽企业重金招聘Python工程师标准>>> Go 1.3 的sync包中加入一个新特性:Pool. 官方文档可以看这里http://golang.org/pkg/sync ...

  7. 深入Golang之sync.Pool详解

    转载地址:https://www.cnblogs.com/sunsky303/p/9706210.html 我们通常用golang来构建高并发场景下的应用,但是由于golang内建的GC机制会影响应用 ...

  8. Go语言之sync.Pool

    什么是sync.Pool 常使用 sync.Pool 来缓存对象 对于很多需要重复分配.回收内存的地方,sync.Pool 是一个很好的选择.频繁地分配.回收内存会给 GC 带来一定的负担,严重的时候 ...

  9. [译] 为什么我们渴求女性来设计 AI

    本文讲的是[译] 为什么我们渴求女性来设计 AI, 原文地址:Why we desperately need women to design AI 原文作者:Kate Brodock 译文出自:掘金翻 ...

最新文章

  1. Django2.0——模板渲染(一)
  2. Safari上使用WebRTC指南
  3. linux java远程调试_idea远程linux代码调试
  4. 计算机系统的组成doc,简述计算机系统的组成.doc
  5. react-redux-store
  6. android studio 上手使用 大水逼问题
  7. OSS实现多文件多线程的断点下载(java)
  8. 场内玩家追赶,场外玩家乐此不疲,场内场外谁主沉浮?
  9. 华为研发模式演进历程
  10. java求圆柱体体积面积(接口继承、字符串常用方法)
  11. Springboot - Ambiguous handler methods mapped
  12. 用友数据库最新会计期间_用友U8数据库维护常用表
  13. 那些年,我开发过的软件
  14. mac下后端开发常用软件
  15. HDU6148 Valley Numer
  16. Matlab获取tif各格点经纬度
  17. 移动硬盘更改驱动器号和路径_如何在Windows 10中更改默认硬盘驱动器以保存文档和应用程序...
  18. Android:rxjava简单实现原理(map/flatmap操作符)
  19. java多重背包代码实现
  20. US-100超声波测距

热门文章

  1. 初学java---第二课《接收控制台(console)输入的方法》
  2. 针对集合中的某个字段对集合中的对象进行排序
  3. 给你的站点全面提速——来自Yahoo UI的各种Bset Practices
  4. C#中窗体的close,dispose,以及application.exit()的区别
  5. 仙剑4按键取钱的东东。
  6. (学习笔记)Oracle表空间相关基本命令
  7. shell编程基础-简述
  8. spark总结——转载
  9. CF617E. XOR and Favorite Number
  10. 深入浅出数据库设计三范式