Dig101-Go 之 interface 呼叫的一個優化點

newbmiao發表於2020-03-04

檢視更多:歷史集錄


Dig101: dig more, simplified more and know more

今天談下上文 ( Dig101-Go 之讀懂 interface 的底層設計 ) 留下的那個問題:

為什麼對於以下 interface Stringer 和構造型別 Binary

下面程式碼conversion會呼叫轉換函式convT64,而devirt不會呼叫?

func conversion() {
  var b Stringer
  var i Binary = 1
  b = i //convT64
  _=b.String()
}

func devirt() {
  var b Stringer = Binary(1)
  _ = b.String() //static call Binary.String
}

這裡可以使用 ssa 視覺化工具檢視,更容易瞭解每行程式碼的編譯過程 如 GOSSAFUNC=main go1.14 build types/interface/interface.go 生成ssa.html ssa.html

事有蹊蹺,必是優化!

搜尋發現相關 issue Devirtualize calls when concrete type behind interface is statically known 和提交 De-virtualize interface calls

原來這個是為了優化如果 interface 內部的構造型別如果可以內聯後被靜態推斷出來的話,就將其直接重寫為靜態呼叫

最初主要希望避免一些 interface 呼叫的 gc 壓力(interface 呼叫在逃逸分析時,會使函式的接受者 (receiver) 和引數 (argument) 逃逸到堆上(而不是留在棧上),增加 gc 壓力。不過這一點目前還未實現,參見Use devirtualization in escape analysis

暫時先優化為靜態呼叫避免轉換呼叫(convXXX),減少程式碼大小和提升細微的效能

摘錄主要處理點如下:

// 對iface=類指標(pointer-shaped)構造型別 記錄itab
// 用於後續優化掉 OCONVIFACE
cmd/compile/internal/gc/subr.go:implements
  if isdirectiface(t0) && !iface.IsEmptyInterface() {
    itabname(t0, iface)
  }
cmd/compile/internal/gc/reflect.go:itabname
  itabs = append(itabs, itabEntry{t: t, itype: itype, lsym: s.Linksym()})
// 編譯前,獲取itabs
cmd/compile/internal/gc/reflect.go:peekitabs
// ssa時利用函式內聯和itabs推斷可重寫為靜態呼叫,避免convXXX
cmd/compile/internal/ssa/rewrite.go:devirt

Go 編譯步驟相關參見 Go compiler

這種優化對於常見的返回 interface 的建構函式還是有幫助的。

func New() Interface { return &impl{...} }

要注意返回構造型別需為類指標才可以。

我們可以利用這一點來應用此 interface 呼叫優化

想了解更多,可以檢視Devirtualize 的測試程式碼

本文程式碼見 NewbMiao/Dig101-Go


文章首發公眾號:newbmiao

推薦閱讀:Dig101-Go 系列

歡迎關注,獲取及時更新內容

更多原創文章乾貨分享,請關注公眾號
  • Dig101-Go 之 interface 呼叫的一個優化點
  • 加微信實戰群請加微信(註明:實戰群):gocnio

相關文章