go 对象池化组件 bytebufferpool使用详解

目录
  • 1. 针对问题
  • 2. 使用方法
  • 3. 源码剖析

1. 针对问题

在编程开发的过程中,我们经常会有创建同类对象的场景,这样的操作可能会对性能产生影响,一个比较常见的做法是使用对象池,需要创建对象的时候,我们先从对象池中查找,如果有空闲对象,则从对象池中移除这个对象并将其返回给调用者使用,只有在池中无空闲对象的时候,才会真正创建一个新对象

另一方面,对于使用完的对象,我们并不会对它进行销毁,而是将它放回到对象池以供后续使用,使用对象池在频繁创建和销毁对象的情况下,能大幅的提升性能,同时为了避免对象池中的对象占用过多的内存,对象池一般还配有特定的清理策略,Go的标准库sync.Pool就是这样一个例子,sync.Pool 中的对象会被垃圾回收清理掉

这类对象中,有一种比较特殊的是字节切片,在做字符串拼接的时候,为了拼接高效,我们通常将中间结果存放在一个字节缓冲中,拼接完之后,再从字节缓冲区生成字符串

Go标准库bytes.Buffer封装字节切片,提供一些使用接口,我们知道切片的容量是有限的,容量不足时需要进行扩容,而频繁的扩容容易造成性能抖动

bytebufferpool实现了自己的Buffer类型,并引入一个简单的算法降低扩容带来的性能损失

2. 使用方法

bytebufferpool的接入很轻量

func main() {
   bf := bytebufferpool.Get()
   bf.WriteString("Hello")
   bf.WriteString(" World!!")
   fmt.Println(bf.String())
}

上面的这种用法使用的是defaultPoolbytebufferpoolPool对象是公开的,也可以自行新建

3. 源码剖析

bytebufferpool是如何做到最大程度减小内存分配和浪费的呢,先宏观的看整个Pool的定义,然后细化到相关的方法,就可以找到答案

bytebufferpoolPool结构体的定义为

type Pool struct {
   calls       [steps]uint64
   calibrating uint64
   defaultSize uint64
   maxSize     uint64
   pool sync.Pool
}

其中calls存储了某一个区间内不同大小对象的个数,calibrating是一个标志位,标志当前Pool是否在重新规划中,defaultSize是元素新建时的默认大小,它的选取逻辑是当前calls中出现次数最多的对象对应的区间最大值,这样可以防止从对象池中捞取之后的频繁扩容,maxSize限制了放入Pool中的最大元素的大小,防止因为一些很大的对象占用过多的内存

bytebufferpool中定义了一些和defaultSizemaxSize计算相关的常量

const (
   minBitSize = 6 // 2**6=64 is a CPU cache line size
   steps      = 20
   minSize = 1 << minBitSize
   maxSize = 1 << (minBitSize + steps - 1)
   calibrateCallsThreshold = 42000
   maxPercentile           = 0.95
)

其中minBitSize表示的是第一个区间对象大小的最大值(2的xx次方-1),在bytebufferpool中,将对象大小分为20个区间,也就是steps,第一个区间为[0, 2^6-1],第二个为[2^6, 2^7-1]...,依此类推

calibrateCallsThreshold表示如果某个区间内对象的数量超过这个阈值,则对Pool中的变量进行重新的计算,maxPercentile用于计算Pool中的maxSize,表示前95%的元素大小

bytebufferpool中的方法也比较少,核心的是GetPut方法

  • Get
func (p *Pool) Get() *ByteBuffer {
   v := p.pool.Get()
   if v != nil {
      return v.(*ByteBuffer)
   }
   return &ByteBuffer{
      B: make([]byte, 0, atomic.LoadUint64(&p.defaultSize)),
   }
}

可以看到,如果对象池中没有对象的话,会申请defaultSize大小的切片返回

  • Put
func (p *Pool) Put(b *ByteBuffer) {
   idx := index(len(b.B))
   if atomic.AddUint64(&p.calls[idx], 1) > calibrateCallsThreshold {
      p.calibrate()
   }
   maxSize := int(atomic.LoadUint64(&p.maxSize))
   if maxSize == 0 || cap(b.B) <= maxSize {
      b.Reset()
      p.pool.Put(b)
   }
}

Put方法会比较麻烦,我们分步来看

  • 计算放入元素在calls数组中的位置
func index(n int) int {
   n--
   n >>= minBitSize
   idx := 0
   for n > 0 {
      n >>= 1
      idx++
   }
   if idx >= steps {
      idx = steps - 1
   }
   return idx
}

这里的逻辑就是先将长度右移minBitSize,如果依然大于0,则每次右移一位,idx加1,最后如果idx超出了总的steps(20),则位置就在最后一个区间

  • 判断当前区间放入元素的个数是否超过了calibrateCallsThreshold指定的阈值,超过则重新计算Pool中元素的值
func (p *Pool) calibrate() {
   // 如果正在重新计算,则返回,控制多并发
   if !atomic.CompareAndSwapUint64(&p.calibrating, 0, 1) {
      return
   }
   // 计算每一段区间中的元素个数 & 元素总个数
   a := make(callSizes, 0, steps)
   var callsSum uint64
   for i := uint64(0); i < steps; i++ {
      calls := atomic.SwapUint64(&p.calls[i], 0)
      callsSum += calls
      a = append(a, callSize{
         calls: calls,
         size:  minSize << i,
      })
   }
   // 按照对象元素的个数从大到小排序
   sort.Sort(a)
   // defaultSize 为内部切片的默认大小,减少扩容次数
   // maxSize 限制放入pool中的最大元素大小
   defaultSize := a[0].size
   maxSize := defaultSize
   // 将前95%元素中的最大size给maxSize
   maxSum := uint64(float64(callsSum) * maxPercentile)
   callsSum = 0
   for i := 0; i < steps; i++ {
      if callsSum > maxSum {
         break
      }
      callsSum += a[i].calls
      size := a[i].size
      if size > maxSize {
         maxSize = size
      }
   }
   // 对defaultSize和maxSize进行赋值
   atomic.StoreUint64(&p.defaultSize, defaultSize)
   atomic.StoreUint64(&p.maxSize, maxSize)
   atomic.StoreUint64(&p.calibrating, 0)
}
  • 判断当前放入元素的大小是否超过了maxSize,超过则不放入对象池中

以上就是go 对象池化组件 bytebufferpool使用详解的详细内容,更多关于go bytebufferpool的资料请关注我们其它相关文章!

(0)

相关推荐

  • Go语言同步等待组sync.WaitGroup结构体对象方法详解

    目录 sync.WaitGroup结构体对象 WaitGroup的结构体 Add()方法 Done()方法 Wait()方法 Add().Done().Wait()三者对比 sync.WaitGroup使用示例 sync.WaitGroup结构体对象 在Go语言中,sync.WaitGroup结构体对象用于等待一组线程的结束:WaitGroup是go并发中最常用的工具,我们可以通过WaitGroup来表达这一组协程的任务是否完成,以决定是否继续往下走,或者取任务结果: WaitGroup的结构体

  • GoFrame框架数据校验之校验结果Error接口对象

    目录 基本介绍 方法示例 注意问题 方法详解 进阶 注意问题 总结 上一篇文章介绍了 GoFrame数据校验之校验对象 | 校验结构体 ,得到了大家积极的反馈. 再接再厉,这篇总结分享:GoFrame数据校验之校验结果总结分享. 基本介绍 校验结果为一个Error接口对象.当数据规则校验成功时,校验方法返回的结果为nil. 当数据规则校验失败时,返回的该对象是包含结构化的层级map,包含多个字段及其规则及对应错误信息,以便于接收端能够准确定位错误规则. 方法示例 首先我们看一下Error的数据结

  • go面向对象方式操作JSON库实现四则运算

    目录 前言 面向对象的方式操作 JSON 实现原理 对 JSON 做四则运算 总结 前言 在之前实现的 JSON 解析器中当时只实现了将一个 JSON 字符串转换为一个 JSONObject,并没有将其映射为一个具体的 struct:如果想要获取值就需要先做断言将其转换为 map 或者是切片再来获,会比较麻烦. decode, err := xjson.Decode(`{"glossary":{"title":"example glossary"

  • GoFrame框架数据校验之校验对象校验结构体

    目录 前言摘要 基本概念 方法介绍 简要说明 注意问题TIPS 链式操作 示例1:单数据校验 简单示例 进阶示例 进阶示例打印结果 示例2:Map数据校验 示例3:Struct数据校验 总结 前言摘要 这篇文章将会为大家介绍GoFrame数据校验中校验对象的知识点,包括:Validator对象常用方法的介绍.单数据校验.校验Map.校验结构体的示例. 基本概念 数据校验组件提供了数据校验对象:用于数据校验统一的配置管理,支持我们便捷的进行链式操作. 方法介绍 type Validator fun

  • Django静态文件配置request对象方法ORM操作讲解

    目录 django框架请求流程 静态文件及相关配置 请求方法 request对象方法 pycharm链接MySQL Django链接MySQL Django ORM ORM语法 ORM外键关联 django框架请求流程 静态文件及相关配置 ORM:对象关系映射 python 映射   数据库类      表 对象     记录对象.属性     字段对应的值'ORM的存在可以使不会MySQL的程序员 使用python的语法操作数据库' 1.先去model.py中编写模型类(相当于是在建表) cl

  • GO语言结构体面向对象操作示例

    目录 匿名字段初始化 成员的操作 同名字段 非结构体匿名字段 结构体指针类型匿名字段 面向过程和对象函数的区别 为结构体类型添加方法 指针变量方法集 普通变量方法集 方法的继承 方法的重写 方法值 方法表达式 匿名字段初始化 package main import "fmt" type Person struct { name string //名字 sex byte //性别 age int //年龄 } type Student struct { Person //只有类型,没有名

  • golang gorm的Callbacks事务回滚对象操作示例

    目录 1. Callbacks 1.1. 创建对象 1.2. 更新对象 1.3. 删除对象 1.4. 查询对象 1.5. 回调示例 1. Callbacks 您可以将回调方法定义为模型结构的指针,在创建,更新,查询,删除时将被调用,如果任何回调返回错误,gorm将停止未来操作并回滚所有更改. 1.1. 创建对象 创建过程中可用的回调 // begin transaction 开始事物 BeforeSave BeforeCreate // save before associations 保存前关

  • go 对象池化组件 bytebufferpool使用详解

    目录 1. 针对问题 2. 使用方法 3. 源码剖析 1. 针对问题 在编程开发的过程中,我们经常会有创建同类对象的场景,这样的操作可能会对性能产生影响,一个比较常见的做法是使用对象池,需要创建对象的时候,我们先从对象池中查找,如果有空闲对象,则从对象池中移除这个对象并将其返回给调用者使用,只有在池中无空闲对象的时候,才会真正创建一个新对象 另一方面,对于使用完的对象,我们并不会对它进行销毁,而是将它放回到对象池以供后续使用,使用对象池在频繁创建和销毁对象的情况下,能大幅的提升性能,同时为了避免

  • 利用Apache Common将java对象池化的问题

    什么是对象池化? 对象被创建后,使用完毕不是立即销毁回收对象,而是将对象放到一个容器保存起来,下次使用的时候不用创建对象,而是从容器中直接获取. 什么样的对象需要池化? 一般需要池化的对象往往都是比"重量级"较的对象,创建和销毁都比较耗时,比如我们的"线程","数据库链接对象","tcp链接对象", "FTP链接对象" 等等. 对象池化的好处? 这些对象池化后,之后使用的时候不用创建,直接使用即可,可以大大

  • Angular2 组件交互实例详解

    1. 组件通信 我们知道Angular2应用程序实际上是有很多父子组价组成的组件树,因此,了解组件之间如何通信,特别是父子组件之间,对编写Angular2应用程序具有十分重要的意义,通常来讲,组件之间的交互方式主要有如下几种: l 使用输入型绑定,把数据从父组件传到子组件 l 通过 setter 拦截输入属性值的变化 l 使用 ngOnChanges 拦截输入属性值的变化 l 父组件监听子组件的事件 l 父组件与子组件通过本地变量互动 l 父组件调用 ViewChild l 父组件和子组件通过服

  • 微信小程序组件 marquee实例详解

    微信小程序组件 marquee实例详解 1. marquee标签 html是有marquee标签的,可以实现跑马灯效果,但小程序没有,所以要实现.这里考虑使用css3的animation实现. html的marquee是这样使用的. <marquee direction="left" behavior="scroll" scrollamount="1" scrolldelay="0" loop="-1"

  • ThreadPoolExecutor线程池原理及其execute方法(详解)

    jdk1.7.0_79 对于线程池大部分人可能会用,也知道为什么用.无非就是任务需要异步执行,再者就是线程需要统一管理起来.对于从线程池中获取线程,大部分人可能只知道,我现在需要一个线程来执行一个任务,那我就把任务丢到线程池里,线程池里有空闲的线程就执行,没有空闲的线程就等待.实际上对于线程池的执行原理远远不止这么简单. 在Java并发包中提供了线程池类--ThreadPoolExecutor,实际上更多的我们可能用到的是Executors工厂类为我们提供的线程池:newFixedThreadP

  • Java编程实现对象克隆(复制)代码详解

    克隆,想必大家都有耳闻,世界上第一只克隆羊多莉就是利用细胞核移植技术将哺乳动物的成年体细胞培育出新个体,甚为神奇.其实在Java中也存在克隆的概念,即实现对象的复制. 本文将尝试介绍一些关于Java中的克隆和一些深入的问题,希望可以帮助大家更好地了解克隆. 假如说你想复制一个简单变量.很简单: int apples = 5; int pears = apples; 不仅仅是int类型,其它七种原始数据类型(boolean,char,byte,short,float,double.long)同样适

  • Hibernate组件映射代码详解

    本文研究的主要是Hibernate组件映射的相关内容,具体如下. 组件关联映射的属性是复杂类型的持久化类,但不是实体类,即数据库中没有表与该属性对应,但该类的属性要持久保存. 比如:外国人的名字name分为firstName和lastName. MyName.java: public class MyName { private String firstName; private String lastName; public String getFirstName() { return fir

  • vue组件与复用详解

    一.什么是组件 组件 (Component) 是 Vue.js 最强大的功能之一.组件可以扩展 HTML 元素,封装可重用的代码. 二.组件用法 组件需要注册后才可以使用,注册有全局注册和局部注册两种方式. 2.1 全局注册后,任何V ue 实例都可以使用.如: <div id="app1"> <my-component></my-component> </div> Vue.component('my-component',{ templ

  • vue全局组件与局部组件使用方法详解

    vue全局/局部注册,以及一些混淆的组件 main.js入口文件的一些常用配置, 在入口文件上定义的public.vue为全局组件,在这里用的是pug模版 .wraper 的形式相当于<div class=wraper></div> -main.js文件 **main.js入口文件的内容** import Vue from 'vue' import App from './App' import router from './router' // 引入公用组件的vue文件 他暴漏的

  • vue 注册组件的使用详解

    一.介绍 组件系统是Vue.js其中一个重要的概念,它提供了一种抽象,让我们可以使用独立可复用的小组件来构建大型应用,任意类型的应用界面都可以抽象为一个组件树 那么什么是组件呢? 组件可以扩展HTML元素,封装可重用的HTML代码,我们可以将组件看作自定义的HTML元素. 二.如何注册组件 Vue.js的组件的使用有3个步骤:创建组件构造器.注册组件和使用组件. 下面用代码演示这三步 <!DOCTYPE html> <html> <body> <div id=&q

随机推荐