为何Gin使用Sync.Pool呢?到底解决框架本身什么性能瓶颈呢?

简介: 为何Gin使用Sync.Pool呢?到底解决框架本身什么性能瓶颈呢?

640.png

为什么Gin使用Sync.Pool呢



Sync.Pool准备用两篇文章给大家讲解,第一篇也就是本篇主要讲解什么是Pool,为什么使用它以及那些框架或者标准库使用它;第二篇就深入源码为大家讲解Pool利用Ringbuffer双向链表解决无锁并发问题。


1. 什么是sync.Pool


sync.Pool 是 Golang 内置的对象池技术,可用于缓存临时对象,避免因频繁建立临时对象所带来的消耗以及对 GC 造成的压力。在许多知名的开源库中,都可以看到 sync.Pool 的大量使用。例如,HTTP 框架 Gin 用 sync.Pool 来复用每个请 求都会创建的 gin.Context 对象。在 fmt、echo、fasthttp 等也都可以看到 sync.Pool 的身影


2. 为什么使用sync.Pool


golang提供了自动GC功能,好处是开发人员不需要考虑内存的分配释放,提升了构建程序的效率,但是在频繁分配内存的 场景中,GC的负载也会上升,表现为GC延迟高、程序性能下降。这个时候内建的 sync.Pool 是一个很好的选择。它 可以将暂时不用的对象缓存起来,待下次需要的时候直接使用,不用再次经过内存分配,复用对象的内存,减轻 GC 的压 力,提升系统的性能。


2.1 单元测试的表现


type Person struct {
  Age int
}
var personPool = sync.Pool{
  New: func() interface{} { return new(Person) },
}
//没有使用Sync.Pool的
func BenchmarkWithoutPool(b *testing.B) {
  var p *Person
  b.ReportAllocs()
  b.ResetTimer()
  for i := 0; i < b.N; i++ {
    for j := 0; j < 10000; j++ {
      p = new(Person)
      p.Age = 23 
    }
  } 
}
//带有Sync.Pool的对象
func BenchmarkWithPool(b *testing.B) {
  var p *Person
  b.ReportAllocs()
  b.ResetTimer()
  for i := 0; i < b.N; i++ {
    for j := 0; j < 10000; j++ {
      p = personPool.Get().(*Person)
      p.Age = 23
      personPool.Put(p)
    }
  } 
}

结果:

640.png


2.2 结论


我们可以看到当程序中频繁申请对象的时候,利用Sync.Pool对象池化技术比不使用Pool技术的程序性能高出两倍,而且在占用内存方面池化技术因为复用内存对象而消耗的内存是0B没有使用Pool池化的程序消耗内存是1.6w B内存。明眼人都能看出来利用池化技术带来的性能提升,不管是消耗时间上还是内存分配上


3. Sync.Pool在Gin中的使用


640.png

一个典型的web service流程如下:

  1. Gin server接受requset,从context pool中取出context赋值给gin.Context
  2. Gin将context传入http handler开始业务逻辑处理
  3. 经过一系列流程后,我们可能需要以异步的方式开启goroutine继续处理业务逻辑,比如分发消息到mq等,但是这里有问题哈,我们下面会说到。
  4. 在流程末尾,将对象归还pool以便重用。


步骤3这里的问题主要是ctx对象无法做到复用了,这里因为context在流程末尾已经被gin回收到pool中,此时context的状态是未知的,可能被回收或者被重置后放到其他 groutine中,在异步任务中读取会引起业务错误。


解决方案:

  1. 在需要异步的地方copy对象
  2. 使用其他的pool组件或者自己实现


Gin框架因为需要给每个请求分配Context,当百万并发到来时,频繁的创建对象会给golang的GC带来非常大的压力,因此Gin作者就利用Pool技术将Context对象复用起来,这样不但可以提升性能,而且在一定程度上可以缓解GC的压力。


4. 别的库也使用到了Sync.Pool


fmtechofasthttp等。

fmt包中的使用:

640.png


ppFree通过Get方法获取一个对象,在Sprintf中使用这个对象,使用完成之后调用free释放掉,即归还给Pool中。


5. 小结


Sync.Pool使用起来非常简单,大家可以结合自己的场景去使用,相信会给你们带来不一样的惊喜。下篇文章重点介绍Pool利用ring buffer和双向链表技术解决无锁并发问题,欢迎大家关注,点赞和转发。

相关文章
|
Linux Anolis
性能优化特性之:EXT4 Fast Commit
本文介绍了倚天实例上进行IO优化的特性:Fast Commit,并对其优化原理、使用方法进行了详细阐述
|
3天前
|
缓存 关系型数据库 MySQL
MySQL并发支撑底层Buffer Pool机制详解
【10月更文挑战第18天】在数据库系统中,磁盘IO操作是性能瓶颈之一。为了提高数据访问速度,减少磁盘IO,MySQL引入了缓存机制。其中,Buffer Pool是InnoDB存储引擎中用于缓存磁盘上的数据页和索引页的内存区域。通过缓存频繁访问的数据和索引,Buffer Pool能够显著提高数据库的读写性能。
19 2
|
2月前
|
存储 缓存 安全
go sync.Pool 设计与实现
go sync.Pool 设计与实现
29 2
|
3月前
|
监控 NoSQL Redis
Redis性能优化问题之lazyfree_pending_objects 这个指标有什么作用
Redis性能优化问题之lazyfree_pending_objects 这个指标有什么作用
|
2月前
|
存储 设计模式 Java
Go - 使用 sync.Pool 来减少 GC 压力
Go - 使用 sync.Pool 来减少 GC 压力
58 0
|
5月前
|
存储 缓存 安全
Golang深入浅出之-Go语言中的并发安全容器:sync.Map与sync.Pool
Go语言中的`sync.Map`和`sync.Pool`是并发安全的容器。`sync.Map`提供并发安全的键值对存储,适合快速读取和少写入的情况。注意不要直接遍历Map,应使用`Range`方法。`sync.Pool`是对象池,用于缓存可重用对象,减少内存分配。使用时需注意对象生命周期管理和容量控制。在多goroutine环境下,这两个容器能提高性能和稳定性,但需根据场景谨慎使用,避免不当操作导致的问题。
167 7
|
5月前
|
设计模式 Go 调度
Golang深入浅出之-Go语言中的并发模式:Pipeline、Worker Pool等
【5月更文挑战第1天】Go语言并发模拟能力强大,Pipeline和Worker Pool是常用设计模式。Pipeline通过多阶段处理实现高效并行,常见问题包括数据竞争和死锁,可借助通道和`select`避免。Worker Pool控制并发数,防止资源消耗,需注意任务分配不均和goroutine泄露,使用缓冲通道和`sync.WaitGroup`解决。理解和实践这些模式是提升Go并发性能的关键。
70 2
|
5月前
|
缓存 Java Go
浅谈Golang对象池sync.pool
浅谈Golang对象池sync.pool
71 0
|
存储 缓存 安全
Golang 语言临时对象池 - sync.Pool
Golang 语言临时对象池 - sync.Pool
50 0
|
缓存 安全 Java
GoFrame gpool 对象复用池 | 对比sync.pool
要介绍gpool对象复用池之前,大家有必要先了解一下go原生提供的sync.pool。
220 0