最近一年各大中小廠都在搞"優化",說到優化,目的還是"降本增效",降低成本,增加效益(效率)。
技術層面,也有一些降本增效的常規操作。
比如池化、io緩衝區技術
golang | C# | eg. | |
---|---|---|---|
池化技術 | snnc.Pool | ObjectPool |
前端切圖仔,歸入前端資源池 , 隨用隨取 |
位元組陣列緩衝區 | bytes.Buffer | List |
... |
io緩衝區 | bufio | BufferStream | 適度超前,賽道埋伏 |
sync.Pool位於標準庫,該檔案提供了對臨時物件的重複使用能力, 避免了頻繁的gc, 對並行協程是安全的。
該檔案只有三個控制點:
下面使用基準測試進行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[重用臨時物件]的效能可見一斑。
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
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
利用緩衝區,減少頻繁的系統呼叫研發人員歷來都是公司的成本大頭,技術優化雖然不能增效,但是能降本,減少核數和記憶體條,希望大家都能通過技術優化讓自己不被優化。
本文來自部落格園,作者:{有態度的馬甲},轉載請註明原文連結:https://www.cnblogs.com/JulianHuang/p/17587159.html
歡迎關注我的原創技術、職場公眾號, 加好友談天說地,一起進化