欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

go 對象池化組件 bytebufferpool使用詳解

 更新時間:2022年10月07日 10:35:15   作者:FfFJ  
這篇文章主要為大家介紹了go 對象池化組件 bytebufferpool使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

1. 針對問題

在編程開發(fā)的過程中,我們經常會有創(chuàng)建同類對象的場景,這樣的操作可能會對性能產生影響,一個比較常見的做法是使用對象池,需要創(chuàng)建對象的時候,我們先從對象池中查找,如果有空閑對象,則從對象池中移除這個對象并將其返回給調用者使用,只有在池中無空閑對象的時候,才會真正創(chuàng)建一個新對象

另一方面,對于使用完的對象,我們并不會對它進行銷毀,而是將它放回到對象池以供后續(xù)使用,使用對象池在頻繁創(chuàng)建和銷毀對象的情況下,能大幅的提升性能,同時為了避免對象池中的對象占用過多的內存,對象池一般還配有特定的清理策略,Go的標準庫sync.Pool就是這樣一個例子,sync.Pool 中的對象會被垃圾回收清理掉

這類對象中,有一種比較特殊的是字節(jié)切片,在做字符串拼接的時候,為了拼接高效,我們通常將中間結果存放在一個字節(jié)緩沖中,拼接完之后,再從字節(jié)緩沖區(qū)生成字符串

Go標準庫bytes.Buffer封裝字節(jié)切片,提供一些使用接口,我們知道切片的容量是有限的,容量不足時需要進行擴容,而頻繁的擴容容易造成性能抖動

bytebufferpool實現(xiàn)了自己的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存儲了某一個區(qū)間內不同大小對象的個數(shù),calibrating是一個標志位,標志當前Pool是否在重新規(guī)劃中,defaultSize是元素新建時的默認大小,它的選取邏輯是當前calls中出現(xiàn)次數(shù)最多的對象對應的區(qū)間最大值,這樣可以防止從對象池中撈取之后的頻繁擴容,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表示的是第一個區(qū)間對象大小的最大值(2的xx次方-1),在bytebufferpool中,將對象大小分為20個區(qū)間,也就是steps,第一個區(qū)間為[0, 2^6-1],第二個為[2^6, 2^7-1]...,依此類推

calibrateCallsThreshold表示如果某個區(qū)間內對象的數(shù)量超過這個閾值,則對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數(shù)組中的位置
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),則位置就在最后一個區(qū)間

  • 判斷當前區(qū)間放入元素的個數(shù)是否超過了calibrateCallsThreshold指定的閾值,超過則重新計算Pool中元素的值
func (p *Pool) calibrate() {
   // 如果正在重新計算,則返回,控制多并發(fā)
   if !atomic.CompareAndSwapUint64(&p.calibrating, 0, 1) {
      return
   }
   // 計算每一段區(qū)間中的元素個數(shù) & 元素總個數(shù)
   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,
      })
   }
   // 按照對象元素的個數(shù)從大到小排序
   sort.Sort(a)
   // defaultSize 為內部切片的默認大小,減少擴容次數(shù)
   // 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的資料請關注腳本之家其它相關文章!

相關文章

  • golang?gorm更新日志執(zhí)行SQL示例詳解

    golang?gorm更新日志執(zhí)行SQL示例詳解

    這篇文章主要為大家介紹了golang?gorm更新日志執(zhí)行SQL示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步早日升職加薪
    2022-04-04
  • go語言的變量定義示例詳解

    go語言的變量定義示例詳解

    這篇文章主要為大家介紹了go語言的變量定義示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-12-12
  • Golang技巧之重試機制詳解

    Golang技巧之重試機制詳解

    重試機制是一種在程序執(zhí)行過程中出現(xiàn)錯誤后重新嘗試執(zhí)行程序的一種機制,可以減少程序運行過程中出現(xiàn)的錯誤,從而提高程序的可靠性,本文就來講講Golang中是如何實現(xiàn)重試機制的吧
    2023-05-05
  • go語法入門泛型type?parameters簡稱T(類型形參)兩種場景使用

    go語法入門泛型type?parameters簡稱T(類型形參)兩種場景使用

    這篇文章主要為大家介紹了go語法入門泛型type?parameters簡稱T(類型形參)兩種場景使用示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-09-09
  • go json數(shù)據轉發(fā)的實現(xiàn)代碼

    go json數(shù)據轉發(fā)的實現(xiàn)代碼

    這篇文章主要介紹了go json數(shù)據轉發(fā)的實現(xiàn)代碼,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2019-09-09
  • go語言制作端口掃描器

    go語言制作端口掃描器

    本文給大家分享的是使用go語言編寫的TCP端口掃描器,可以選擇IP范圍,掃描的端口,以及多線程,有需要的小伙伴可以參考下。
    2015-03-03
  • Golang如何實現(xiàn)任意進制轉換的方法示例

    Golang如何實現(xiàn)任意進制轉換的方法示例

    進制轉換是人們利用符號來計數(shù)的方法,進制轉換由一組數(shù)碼符號和兩個基本因素“基數(shù)”與“位權”構成,這篇文章主要給大家介紹了關于Golang如何實現(xiàn)10進制轉換62進制的方法,文中給出了詳細的示例代碼供大家參考學習學習,下面隨著小編來一起學習學習吧。
    2017-09-09
  • 簡單聊聊為什么說Go語言字符串是不可變的

    簡單聊聊為什么說Go語言字符串是不可變的

    最近有讀者留言說,平時在寫代碼的過程中,是會對字符串進行修改的,但網上都說 Go 語言字符串是不可變的,這是為什么呢,本文就來和大家簡單講講
    2023-05-05
  • sublime text3解決Gosublime無法自動補全代碼的問題

    sublime text3解決Gosublime無法自動補全代碼的問題

    本文主要介紹了sublime text3解決Gosublime無法自動補全代碼的問題,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-01-01
  • golang開發(fā)微框架Gin的安裝測試及簡介

    golang開發(fā)微框架Gin的安裝測試及簡介

    這篇文章主要為大家介紹了golang微框架Gin的安裝測試及簡介,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步早日升職加薪
    2021-11-11

最新評論