golang技術降本增效的手段

最近一年各大中小廠都在搞"優化",說到優化,目的還是"降本增效",降低成本,增加效益(效率)。

技術層面,也有一些降本增效的常規操作。

比如池化、io緩衝區技術

golang C# eg.
池化技術 snnc.Pool ObjectPool 前端切圖仔,歸入前端資源池 , 隨用隨取
字節數組緩衝區 bytes.Buffer List ...
io緩衝區 bufio BufferStream 適度超前,賽道埋伏

池化技術 sync.Pool

sync.Pool位於標準庫,該文件提供了對臨時對象的重複使用能力, 避免了頻繁的gc, 對併發協程是安全的。

該文件只有三個控制點:

  • New: 默認的臨時對象
  • Get: 從池中哪一個臨時對象
  • Put: 放回池中,以重用

下面使用基準測試進行b.N*1000次運算時的內存消耗。

package main

import (
	"sync"
	"testing"
)

type Person struct {
	Age int
}

var (
	personPool = sync.Pool{
		New: func() interface{} {  // 設置默認值
			return &Person{}
		},
	}
)

func ExampleObjPool() {
	var p *Person
	for i := 0; i < 1000; i++ {
		for j := 0; j < 1000; j++ {
			p = personPool.Get().(*Person)
			p.Age = i + 1
			personPool.Put(p)
		}
	}
	p = personPool.Get().(*Person)
	fmt.Println(p.Age)
	// output:1000
}

func BenchmarkWithoutPool(b *testing.B) {
	var p *Person
	b.ReportAllocs()
	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		for j := 0; j < 1000; j++ {
			p = new(Person)    // 每次均產生臨時對象
			p.Age = 23
		}
	}
}

func BenchmarkWithPool(b *testing.B) {
	var p *Person
	b.ReportAllocs()
	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		for j := 0; j < 1000; j++ {
			p = personPool.Get().(*Person)  // 從池中複用對象
			p.Age = 23
			personPool.Put(p)     // 放回以重用
		}
	}
}

測試結果如下,sync.Pool[重用臨時對象]的性能可見一斑。

bytes.Buffer

golang很多方法內充斥了[]byte, 就連最常規的序列化/反序列化,返回值/參數都是[]byte, 但是slice是一個冷冰冰的數據結構,沒有得心趁手的操作行爲,還有很多陷阱。

  func Marshal(v any) ([]byte, error)
  func Unmarshal(data []byte, v any) 

A bytes.Buffer is a variable-sized buffer of bytes with Read and Write methods.

坦白講bytes.Buffer並非底層優化機制, 實際提供了一個友好操作slice的方式。

下面的"abcd"字符串,先讀取首字符、後面追加字符"e":

  var b bytes.Buffer
	b.Write([]byte("abcd")) // 寫入之後,自動擴容
	rdbuf := make([]byte, 1)
	_, err := b.Read(rdbuf) // 讀取一個字節的數據,移動讀off指針
	if err != nil {
		panic(err)
	}
	fmt.Println(b.String()) // 上面讀取了一個字符,讀off已經移動,現從讀off位置轉換爲string
	b.WriteByte('e')        // 在尾部寫字符
	fmt.Println(b.String())
	fmt.Printf("%d, %d \n", b.Len(), b.Cap()) // Len方法返回還能讀取的字符數量,Cap返回底層buf的容量
  
//output:
bcd 
bcde
4, 64

bufio

Package bufio implements buffered I/O. It wraps an io.Reader or io.Writer object, creating another object (Reader or Writer) that also implements the interface but provides buffering and some help for textual I/O.

首先我們需要知道當應用程序執行IO操作(從文件、網絡和數據庫讀取或寫入數據),它將觸發底層的系統調用,從性能角度來看,這很繁重。

緩衝IO是一種技術,用於在傳遞之前暫時積累IO操作的結果。這種技術可以通過減少系統調用的數量來提高程序的速度。例如,如果您想要逐字節地從磁盤讀取數據,與每次直接從磁盤讀取每個字節不同,使用緩衝區IO技術,我們可以一次將一個數據塊讀入緩衝區,然後消費者可以以任何您想要的方式從緩衝區讀取數據。通過減少繁重的系統調用,性能將得到提高。

磁盤:1.尋址:ms(毫秒) 2.磁盤帶寬:MB/s
內存:1.尋址:ns(納秒) 2. 內存帶寬:GB/s
磁盤比內存在尋址上慢了10W倍,傳輸帶寬上慢了20倍。

開源的帶緩衝區的logrus日誌寫入hook,就利用了bufio技術。

 // 利用bufio針對原始io.Writer封裝成帶緩衝區的io.Writer  
 `s.writer = bufio.NewWriterSize(s.Writer, size) 
  ......
  if len(bs) > s.writer.Available() && s.writer.Buffered() > 0 {
		if err := s.writer.Flush(); err != nil {
			return err
		}
	}
	_, err = s.writer.Write(bs)` 

優化總結

  • sync.Pool 複用臨時對象,減少gc次數
  • bufio利用緩衝區,減少頻繁的系統調用

研發人員歷來都是公司的成本大頭,技術優化雖然不能增效,但是能降本,減少核數和內存條,希望大家都能通過技術優化讓自己不被優化。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章