在PHP中,將“interface{}”類型轉換為切片(slice)類型時,會導致額外的堆分配。這是因為在PHP中,接口(interface)是一種抽象的數據類型,而切片是一種動態數組類型。當我們將接口類型轉換為切片類型時,PHP需要為切片類型分配額外的內存空間來存儲切片的元素。這個額外的堆分配操作會導致內存的額外開銷,對于一些內存敏感的應用程序來說,可能會帶來性能問題。因此,在進行類型轉換時,我們應該注意這個問題,盡量避免不必要的額外堆分配。
問題內容
func benchmarkpool(b *testing.b) { b.reportallocs() p := sync.pool{new: func() interface{} { return make([]byte, 1024) }} for i := 0; i < b.n; i++ { bts := p.get().([]byte) p.put(bts) } }
登錄后復制
該基準測試在 go1.19.5 中給出以下輸出。
benchmarkpool benchmarkpool-10 47578498 24.47 ns/op 24 b/op 1 allocs/op
登錄后復制
當使用 *[]byte
時,事情會變得不同:
func benchmarkpool(b *testing.b) { b.reportallocs() p := sync.pool{new: func() interface{} { bts := make([]byte, 1024) return &bts }} for i := 0; i < b.n; i++ { bts := p.get().(*[]byte) p.put(bts) } }
登錄后復制
BenchmarkPool BenchmarkPool-10 142008002 8.581 ns/op 0 B/op 0 allocs/op
登錄后復制
似乎將 interface{}
轉換回切片會導致額外的堆分配。
為什么 go 需要這個額外的分配?其背后的設計考慮是什么?
解決方法
造成分配的不是 any
到 []byte
的轉換,而是 []byte
到 any
的轉換。 p.Put(bts)
將參數 bts
隱式轉換為 any
,然后再將其傳遞給 (*sync.Pool).Put
。 GoGC 1.19 中的接口被實現為一對指針,一個指向類型元數據,一個指向實際對象,在這種情況下,第二個指針轉義到池,導致分配切片對象。這不僅適用于切片類型,也適用于任何其他非指針類型。
對于指針,例如 *[]byte
,編譯器會執行優化,將其值直接放入 iface
結構中,從而在轉換為接口時刪除 *[]byte
實例的分配。因此,通常建議將指針放入池中而不是結構本身。