這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
這篇文章想聊聊如何給gc減負的問題,也即我們在寫業務的時候,有時候需要考慮下gc老人家的感受,但又不能喪失代碼的可讀性,有些情況下代碼需不需要最佳化,最佳化後能取得多大的效能提升,其中的平衡需要把握。不然很容易出現:脫褲子放屁,多此一舉。
- 字串拼接,demo代碼:
package mainimport ( "bytes")func f1(l int) { var s, s1 string = ``, `hello world` for i := 0; i < l; i++ { s = s + s1 }}func f2(l int) { buf := bytes.NewBuffer([]byte{}) var s1 string = `hello world` for i := 0; i < l; i++ { buf.WriteString(s1) }}func f3(l int) { var s []string var s1 string = `hello world` for i := 0; i < l; i++ { s = append(s, s1) } strings.Join(s, ``)}
測試代碼:
package mainimport ( "testing")func Benchmark_F1(b *testing.B) { for i := 0; i < b.N; i++ { f1(100000) }}func Benchmark_F2(b *testing.B) { for i := 0; i < b.N; i++ { f2(100000) }}func Benchmark_F3(b *testing.B) { for i := 0; i < b.N; i++ { f3(100000) }}
go test -bench=".*" -test.benchmem -count=1 輸出的結果是:
Benchmark_F1-4 1 9334113815 ns/op 55401130720 B/op 100056 allocs/opBenchmark_F2-4 1000 2318146 ns/op 2891600 B/op 18 allocs/opBenchmark_F3-4 100 14660804 ns/op 11459184 B/op 32 allocs/opPASSok _/Users/taomin/pprof/string 13.394s
從bench的結果來看,10W個字串的拼接,+的最差,bytes.NewBuffe最好。記憶體和時間上的差距大概在3個數量級左右。這種字串拼接的最佳化,除非你的情境確實存在大量字串拼接,不然不要使用什麼bytes或者strings.join,直接+拼接起來就好了。
- 臨時對象池
對象池將對象存放到池裡,通過複用之前的對象,從而減少指派至的個數。每次GC,runtime會調用poolCleanup函數來將Pool清空,這樣原本Pool中儲存的對象會被GC全部回收。這是Pool的一個特性,這個特性會導致:有狀態的對象不能儲存在Pool中,Pool不能用作串連池;官方文檔說明如下:
A Pool is a set of temporary objects that may be individually saved and retrieved.Any item stored in the Pool may be removed automatically at any time without notification. If the Pool holds the only reference when this happens, the item might be deallocated.A Pool is safe for use by multiple goroutines simultaneously.Pool's purpose is to cache allocated but unused items for later reuse, relieving pressure on the garbage collector. That is, it makes it easy to build efficient, thread-safe free lists. However, it is not suitable for all free lists.An appropriate use of a Pool is to manage a group of temporary items silently shared among and potentially reused by concurrent independent clients of a package. Pool provides a way to amortize allocation overhead across many clients.An example of good use of a Pool is in the fmt package, which maintains a dynamically-sized store of temporary output buffers. The store scales under load (when many goroutines are actively printing) and shrinks when quiescent.On the other hand, a free list maintained as part of a short-lived object is not a suitable use for a Pool, since the overhead does not amortize well in that scenario. It is more efficient to have such objects implement their own free list.A Pool must not be copied after first use.type Pool struct { // New optionally specifies a function to generate // a value when Get would otherwise return nil. // It may not be changed concurrently with calls to Get. New func() interface{} // contains filtered or unexported fields}
在網上有哥們寫的Pool的例子,覺得很能說明問題,這裡用他的代碼作為例子來說明:
package mainimport ( "fmt" "io" "net/http" "sync")// 並發過程使用了多少次 []bytevar mu sync.Mutexvar holder map[string]bool = make(map[string]bool)// 臨時對象池var p = sync.Pool{ New: func() interface{} { buffer := make([]byte, 1024) return &buffer },}func readContent(wg *sync.WaitGroup) { defer wg.Done() resp, err := http.Get("http://my.oschina.net/xinxingegeya/home") if err != nil { fmt.Println(err) } defer resp.Body.Close() byteSlice := p.Get().(*[]byte) //類型斷言 key := fmt.Sprintf("%p", byteSlice) mu.Lock() _, ok := holder[key] if !ok { holder[key] = true } mu.Unlock() _, err = io.ReadFull(resp.Body, *byteSlice) if err != nil { fmt.Println(err) } p.Put(byteSlice)}func main() { var wg sync.WaitGroup for i := 0; i < 10; i++ { wg.Add(1) go readContent(&wg) } wg.Wait() for key, val := range holder { fmt.Println("Key:", key, "Value:", val) }}
Pool要慎用哦,因為它的脾氣不太好。
- string轉位元組數組
string類型的變數裡面的值會被複製到位元組數組中,雨虹學堂上之前一種最佳化思路,通過unsafe包的指標操作,直接對string的地址進行操作,這樣避免了記憶體複製操作,但個人感覺這個最佳化有點過了,這會讓代碼失去可讀性,例如:
s := "hello world!"b := []byte(s)
被改成了:
func str2bytes(s string) []byte { x := (*[2]uintptr)(unsafe.Pointer(&s)) h := [3]uintptr{x[0], x[1], x[1]} return *(*[]byte)(unsafe.Pointer(&h))}
下面的代碼如果不好好研究下,完全看不懂在搞神馬。以上這些是自己能想到的最佳化點,你有什麼好的最佳化方案和經曆,歡迎分享。。。
end ~