golang sync.Pool的数据模型在1.13中发生了比较大的改变,一方面是数据模型的重构,一方面是GC对sync.Pool池子影响的优化。

1.数据模型:

初始情况

  • 默认大小为8,能放8个item。

  • 因为是有限大小的FIFO,所以采取了最佳模型ring buffer来实现这个队列。(因为定长队列是用定长数组实现的,如果在头部push的话,需要后续所有内容往后迁移,复杂度为O(n),而使用环状的话则复杂度为O(1)。)

如图:

(1)O(n)——普通队列
sync.Pool设计分析

(2)O(1)——环状队列
sync.Pool设计分析

https://en.wikipedia.org/wiki/Circular_buffer

  • head端只能一个producer,即当前线程,进行push和pop,tail端可以被多个comsumer,即其他所有线程,进行pop操作。(多个comsumer并发读取采用的是atomic加锁的方式限制,如每次pop时对index进行atomic.CompareAndSwapUint64操作,比较轻量)。

  • tail的每次读取都会删除该项,将这一项变成nil。(也就是说如果读取的速度足够快,则始终就在这大小为8的环里运行,节省空间)

  • head的每次写入val,即使是nil,也会转变为*struct{}作为标记。

sync.Pool设计分析

如果写满了

  • 如果写的速度比较快,写满了之后,则根据双向链表的方式,引出一个2倍大小的ring buffer(每次都会翻倍,直到达到最大为1<<32 / 4则不再增加)。
  • 当前的ring如果pop head用完了,则会根据prev查看上一个ring
  • 如果tail pop完了之后,则会删除这个ring,随后指向新的ring(因为后续不再使用)

sync.Pool设计分析

sync.Pool1.13模型设计亮点:

  1. 使用了ring buffer(定长FIFO) + 双向链表的方式,头部只能当前线程读取与写入,尾部可以并发读取,相较于1.12版一个数组,加上一个大锁限制,轻量的多,效率高了许多。
  2. head与tail的index使用一个uint64存储,前32位是head,后32位是tail。通过掩码以及位运算获取他俩,然后在atomic.CompareAndSwapUint64的操作中效率更高,一个操作就能比较head和tail的index是否发生了改变。

2. GC优化

https://medium.com/a-journey-with-go/go-understand-the-design-of-sync-pool-2dde3024e277

之前每次GC时都会清空pool,而在1.13版本中引入了victim cache,会将pool内数据拷贝一份,避免GC将其清空,即使没有引用的内容也可以保留最多两轮GC。

相关性能测试:

https://www.jianshu.com/p/cf3ac244e222

相关文章:

  • 2021-07-26
  • 2021-06-27
  • 2021-12-25
  • 2021-06-29
  • 2022-12-23
  • 2022-12-23
  • 2021-11-04
  • 2021-12-19
猜你喜欢
  • 2022-12-23
  • 2021-06-23
  • 2021-08-12
相关资源
相似解决方案