首页 > 其他分享 >Go sync.Pool

Go sync.Pool

时间:2023-03-18 15:35:49浏览次数:43  
标签:sync stu func Go buf Pool op

1 sync.Pool 的使用场景

一句话总结:保存和复用临时对象,减少内存分配,降低 GC 压力。

 

举个简单的例子:

type Student struct {
	Name   string
	Age    int32
	Remark [1024]byte
}

var buf, _ = json.Marshal(Student{Name: "Geektutu", Age: 25})

func unmarsh() {
	stu := &Student{}
	json.Unmarshal(buf, stu)
}

json 的反序列化在文本解析和网络通信过程中非常常见,当程序并发度非常高的情况下,短时间内需要创建大量的临时对象。而这些对象是都是分配在堆上的,会给 GC 造成很大压力,严重影响程序的性能。

参考:垃圾回收(GC)的工作原理

Go 语言从 1.3 版本开始提供了对象重用的机制,即 sync.Pool。sync.Pool 是可伸缩的,同时也是并发安全的,其大小仅受限于内存的大小。sync.Pool 用于存储那些被分配了但是没有被使用,而未来可能会使用的值。这样就可以不用再次经过内存分配,可直接复用已有对象,减轻 GC 的压力,从而提升系统的性能。

sync.Pool 的大小是可伸缩的,高负载时会动态扩容,存放在池中的对象如果不活跃了会被自动清理。

2 如何使用

sync.Pool 的使用方式非常简单:

2.1 声明对象池

只需要实现 New 函数即可。对象池中没有对象时,将会调用 New 函数创建。

var studentPool = sync.Pool{
    New: func() interface{} { 
        return new(Student) 
    },
}

  


2.2 Get & Put

stu := studentPool.Get().(*Student)
json.Unmarshal(buf, stu)
studentPool.Put(stu)

  


  • Get() 用于从对象池中获取对象,因为返回值是 interface{},因此需要类型转换。
  • Put() 则是在对象使用完毕后,返回对象池。

3 性能测试

3.1 struct 反序列化

func BenchmarkUnmarshal(b *testing.B) {
	for n := 0; n < b.N; n++ {
		stu := &Student{}
		json.Unmarshal(buf, stu)
	}
}

func BenchmarkUnmarshalWithPool(b *testing.B) {
	for n := 0; n < b.N; n++ {
		stu := studentPool.Get().(*Student)
		json.Unmarshal(buf, stu)
		studentPool.Put(stu)
	}
}

  


测试结果如下:

$ go test -bench . -benchmem
goos: darwin
goarch: amd64
pkg: example/hpg-sync-pool
BenchmarkUnmarshal-8           1993   559768 ns/op   5096 B/op 7 allocs/op
BenchmarkUnmarshalWithPool-8   1976   550223 ns/op    234 B/op 6 allocs/op
PASS
ok      example/hpg-sync-pool   2.334s

  


在这个例子中,因为 Student 结构体内存占用较小,内存分配几乎不耗时间。而标准库 json 反序列化时利用了反射,效率是比较低的,占据了大部分时间,因此两种方式最终的执行时间几乎没什么变化。但是内存占用差了一个数量级,使用了 sync.Pool 后,内存占用仅为未使用的 234/5096 = 1/22,对 GC 的影响就很大了。

3.2 bytes.Buffer

var bufferPool = sync.Pool{
	New: func() interface{} {
		return &bytes.Buffer{}
	},
}

var data = make([]byte, 10000)

func BenchmarkBufferWithPool(b *testing.B) {
	for n := 0; n < b.N; n++ {
		buf := bufferPool.Get().(*bytes.Buffer)
		buf.Write(data)
		buf.Reset()
		bufferPool.Put(buf)
	}
}

func BenchmarkBuffer(b *testing.B) {
	for n := 0; n < b.N; n++ {
		var buf bytes.Buffer
		buf.Write(data)
	}
}

  


测试结果如下:

BenchmarkBufferWithPool-8    8778160    133 ns/op       0 B/op   0 allocs/op
BenchmarkBuffer-8             906572   1299 ns/op   10240 B/op   1 allocs/op

  


这个例子创建了一个 bytes.Buffer 对象池,而且每次只执行一个简单的 Write 操作,存粹的内存搬运工,耗时几乎可以忽略。而内存分配和回收的耗时占比较多,因此对程序整体的性能影响更大。

4 在标准库中的应用

4.1 fmt.Printf

Go 语言标准库也大量使用了 sync.Pool,例如 fmt 和 encoding/json

以下是 fmt.Printf 的源代码(go/src/fmt/print.go):

// go 1.13.6

// pp is used to store a printer's state and is reused with sync.Pool to avoid allocations.
type pp struct {
    buf buffer
    ...
}

var ppFree = sync.Pool{
	New: func() interface{} { return new(pp) },
}

// newPrinter allocates a new pp struct or grabs a cached one.
func newPrinter() *pp {
	p := ppFree.Get().(*pp)
	p.panicking = false
	p.erroring = false
	p.wrapErrs = false
	p.fmt.init(&p.buf)
	return p
}

// free saves used pp structs in ppFree; avoids an allocation per invocation.
func (p *pp) free() {
	if cap(p.buf) > 64<<10 {
		return
	}

	p.buf = p.buf[:0]
	p.arg = nil
	p.value = reflect.Value{}
	p.wrappedErr = nil
	ppFree.Put(p)
}

func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
	p := newPrinter()
	p.doPrintf(format, a)
	n, err = w.Write(p.buf)
	p.free()
	return
}

// Printf formats according to a format specifier and writes to standard output.
// It returns the number of bytes written and any write error encountered.
func Printf(format string, a ...interface{}) (n int, err error) {
	return Fprintf(os.Stdout, format, a...)
}

  


fmt.Printf 的调用是非常频繁的,利用 sync.Pool 复用 pp 对象能够极大地提升性能,减少内存占用,同时降低 GC 压力。

这个例子来源于:深度解密 Go 语言之 sync.Pool

标签:sync,stu,func,Go,buf,Pool,op
From: https://www.cnblogs.com/gongxianjin/p/17230860.html

相关文章

  • go判断远程文件是否存在RemoteFileExist
    funcIsFileExist(filepathstring)bool{//ifremotefileis//ls-l|grepaa|wc-lfileName:=path.Base(filepath)//aafileDirName:=path.Dir(......
  • Vue3 + go + axios 的前后端交互
    下载npminstallaxiosaxios全局配置创建一个js文件用于保存axios设置,配置文件自行搜索在main.js中使用保存好的axios设置```jsimportaxiosfrom'@/plugins/axi......
  • go 类型别名
    1.背景类型别名(typealiases)原本是要在Go1.8发布时推出的。但是由于一些争议和实现上的问题,Go团队把它推迟到了Go1.9。2.目的这一特性其实是为开发者们的代码库......
  • 过滤组件的使用--django_filter模块实现多条件组合查询
    1.过滤组件的使用(实现条件查询)_第一步_安装django_filter:  2.过滤组件的使用(实现条件查询),第二步:注册django_filters:  3.过滤组件的使用(实现条件查询),第三步:过......
  • 0008 ALGO999-数的潜能
    试题算法训练数的潜能可以转换为将数分解为多少个3,再处理余数即可。为什么不分解为2,因为23=8<9=32。加上较小值得处理,输入值\(\le4\)时,直接输出即可。......
  • 对并发熟悉吗?说说synchronized及实现原理
    synchronized的基本使用synchronized是Java中解决并发问题的一种最常用的方法,也是最简单的一种方法。作用主要有三个:确保线程互斥的访问同步代码保证共享变量的修改能......
  • synchronized 与 volatile 关键字
    目录​​1.前言​​​​1.synchronized关键字​​​​1.互斥​​​​2.保证内存可见性​​​​3.可重入​​​​2.volatile关键字​​​​1.保证内存可见性​​​​2.无......
  • django 迁移数据报错:django.db.utils.OperationalError: (1050, "Table 'xxx' alread
    方法1:登录数据库删除掉django创建数据表的所有数据内容、或者直接删掉所有表格。DROPTABLEtable_name;因为MySQL中设置了foreignkey关联,造成无法更新或删除数据。......
  • 说一下线程池内部工作原理(ThreadPoolExecutor)
    ThreadPoolExecutor构造方法的参数corePoolSize:线程池的核心线程数,说白了就是,即便是线程池里没有任何任务,也会有corePoolSize个线程在候着等任务。maximumPoolSize:最大......
  • golang使用缓存库go-cache的测试用例-短期内存缓存数据类似memcache/redis-【唯一客服
    golang中使用go-cache是非常普遍的,比如,我在对接微信客服接口的时候,获取access_token,默认获取一次有两个小时的有效期这个时候,我就可以使用go-cache来缓存access_token了......