一文入門 Go 靜態單賦值(SSA)

在上一篇文章《通過實例理解 Go 內聯優化》[1] 中,我們探討了 Go 編譯器在編譯中端進行的內聯優化。內聯優化基於 IR 中間表示進行,不過 Go 編譯過程不止有一種 IR 表示,這點和龍書《編譯原理 (第二版)》[2] 的在第六章 “中間代碼生成” 一開始處的講解是一致的,即在將給定源語言的一個程序翻譯成特定的目標機器代碼的過程中,一個編譯器可能構造出一系列中間表示(IR),如下圖:

高層中間表示更接近於源語言,而低層的中間表示則更接近於目標機器。在 Go 編譯過程中,如果說內聯優化使用的 IR 是高層中間表示,那麼低層中間表示非支持靜態單賦值 (SSA) 的中間代碼形式莫屬。

在這一篇中,我們將沿着 Go 編譯器的後端優化之路繼續走下去,我們來認識一下靜態單賦值 (SSA)

1. 靜態單賦值 (SSA) 的歷史

靜態單賦值 (Static Single Assignment,SSA),也有稱爲 Single Static Assignment 的,是一種 ** 中間代碼的表示形式 (IR)**,或者說是某種中間代碼所具備的屬性,它是由 IBM 的三位研究員:Barry K. Rosen、Mark N. Wegman 和 F. Kenneth Zadeck 於 1988 年提出的。

具有 SSA 屬性的 IR 都具有這樣的特徵:

下面是一個簡單的例子 (僞代碼):

y = 1
y = 2
x = y

轉換爲 SSA 形式爲:

y1 = 1
y2 = 2
x1 = y2

我們看到由於 SSA 要求每個變量只能賦值一次,因此在轉換爲 SSA 後,變量 y 用 y1 和 y2 來表示,後面的序號越大,表明 y 的版本越新。從這一段三行的代碼我們也可以看到,在 SSA 層面,y1 = 1 這行代碼就是一行死代碼 (dead code),即對結果不會產生影響的代碼,可以在中間代碼優化時被移除掉。

1991 年,同樣來自 IBM 研究院的 Ron Cytron 和 Jeanne Ferrante 以及前面的三位研究員又一起給出了構建 SSA 的快速算法 [3],這進一步推動了 SSA 在編譯器領域的快速應用。

SSA 的提出以及後續的流行正是因爲 SSA 形式中間代碼具有很好的優化空間,基於 SSA 可以開啓一些新的編譯器優化算法或增強現有的優化算法,因此自 SSA 提出後,各種主流語言編譯器後端均逐漸開始支持 SSA,包括 GCC、llvm、hotspot JVM、v8 js 等。SSA 也成爲了一種 IR 表示的事實標準。

那麼 Go 語言是何時開始與 SSA 結緣的呢?我們繼續往下看。

2. Go 與 SSA

相對於 GCC、LLVM,Go 編譯器還相對年輕 [4],因此 SSA 加入 Go 的時間還不算太長。

Go SSA 的工作始於 Go 1.5 版本 [5] 實現自舉之前,2015 年 2 月初,負責編譯器後端的 Go 團隊核心成員的 Keith Randall 博士就在 golang-dev google group[6] 上提出要讓 Go 支持 SSA 的工作計劃:

“我想從目前基於語法樹的IR轉換到更現代的基於SSA的IR。有了SSA IR,我們可以實現很多在當前編譯器中難以做到的優化” - Keith Randall

同期,Keith Randall 博士還編寫了 “New SSA Backend for the Go Compiler”[7] 文檔,具體介紹了 Go 要支持 SSA 的理由以及分幾步走的實現方案。

在爲什麼選擇自己實現 SSA IR,而不是轉換爲當時現成的諸如 gcc, llvm 等支持的 IR 形式並利用成熟後端進行中間代碼優化這個問題上,Keith Randall 博士給出了三點理由:

2016 年 3 月 1 日,在 Go 1.7 版本的 master 分支提交權限剛剛打開之後,Keith Randall 就將支持 ssa 的 dev.ssa 分支合併到 Go 項目主線中了。

在 Go 1.7 版本 [8] 中,Go 正式支持 SSA,不過由於時間有限,Go 1.7 SSA 僅支持針對 amd64 架構的優化。即便如此,Go 支持 SSA 後,Keith Randall 的 benchmark 顯示性能提升 12%,代碼段縮小 13%:

圖:go 1.7 benchmark(圖來自 keith 博士的 slide)

Go 1.7 正式發佈時,其發佈文檔稱 Go 程序的性能因對 SSA 的支持而提升 5%-35% 以上 [9]。由此看,Go SSA 的實現達到了 Keith Randall 博士的預期目標,也爲 Go 編譯器後續的持續優化奠定了基礎。

在 2017 年 2 月發佈的 Go 1.8 版本 [10] 中,Go SSA 的支持範圍擴展到其他所有 Go 支持的 cpu 架構,包括 arm 和 arm64、mips 和 mips64、ppc64 等。

瞭解了 Go SSA 的演進後,我們再來簡單說說 Go 編譯器中 SSA 的實現。

3. 轉換爲 SSA

我們先來看看轉換爲 SSA 以及 SSA 優化在編譯過程中所處的位置:

圖:Go SSA 所處的環節 (圖來自 keith 博士的 slide)

上圖是 keith 博士在 2017 年 gophercon 大會上 slide 中的一幅圖,這幅圖中明確了生成 SSA 形式以及 SSA 優化所處的環節。不過較新的 Go 版本中,convert to SSA 之前也有一種不同於最初的抽象語法樹的 ir(比如:Go 1.19),SSA 是由此種 ir 轉換過來的。

從代碼上來看,ir 到 SSA 形式的轉換髮生在下面環節 (Go 1.19 版本代碼,其他版本可能代碼位置和內容均由不同):

// $GOROOT/src/cmd/compile/internal/gc/main.go
func Main(archInit func(*ssagen.ArchInfo)) {
    base.Timer.Start("fe""init")

    defer handlePanic()

    archInit(&ssagen.Arch)
    ... ...

    // Compile top level functions.
    // Don't use range--walk can add functions to Target.Decls.
    base.Timer.Start("be", "compilefuncs")
    fcount := int64(0)
    for i := 0; i < len(typecheck.Target.Decls); i++ {
        if fn, ok := typecheck.Target.Decls[i].(*ir.Func); ok {
            // Don't try compiling dead hidden closure.
            if fn.IsDeadcodeClosure() {
                continue
            }
            enqueueFunc(fn)
            fcount++
        }
    }
    base.Timer.AddEvent(fcount, "funcs")

    compileFunctions()

    ... ...
}

在 Main 中,我們看到代碼會將所有 Target.Decls(函數) 通過 enqueueFunc 入隊列 (compilequeue),然後調用 compileFunctions 來實現各個函數從 AST ir 到 SSA 形式的轉換,compileFunctions 在 compile.go 中,其實現如下:

// $GOROOT/src/cmd/compile/internal/gc/compile.go
func compileFunctions() {
    if len(compilequeue) == 0 {
        return
    }

    ... ...
    // By default, we perform work right away on the current goroutine
    // as the solo worker.
    queue := func(work func(int)) {
        work(0)
    }
    ... ...

    var compile func([]*ir.Func)
    compile = func(fns []*ir.Func) {
        wg.Add(len(fns))
        for _, fn := range fns {
            fn := fn
            queue(func(worker int) {
                ssagen.Compile(fn, worker)
                compile(fn.Closures)
                wg.Done()
            })
        }
    }
    types.CalcSizeDisabled = true // not safe to calculate sizes concurrently
    base.Ctxt.InParallel = true

    compile(compilequeue) 
    ... ...
}

在 compileFunctions 中我們看到,編譯器從 compilequeue 取出 AST IR 形式的函數,並調用 ssagen.Compile 將其編譯爲 SSA 形式。下面是 ssagen.Compile 的代碼:

// $GOROOT/src/cmd/compile/internal/ssagen/pgen.go

// Compile builds an SSA backend function,
// uses it to generate a plist,
// and flushes that plist to machine code.
// worker indicates which of the backend workers is doing the processing.
func Compile(fn *ir.Func, worker int) {
    f := buildssa(fn, worker)
    // Note: check arg size to fix issue 25507.
    if f.Frontend().(*ssafn).stksize >= maxStackSize || f.OwnAux.ArgWidth() >= maxStackSize {
        largeStackFramesMu.Lock()
        largeStackFrames = append(largeStackFrames, largeStack{locals: f.Frontend().(*ssafn).stksize, args: f.OwnAux.ArgWidth(), pos: fn.Pos()})
        largeStackFramesMu.Unlock()
        return
    }
    pp := objw.NewProgs(fn, worker)
    defer pp.Free()
    genssa(f, pp)
    // Check frame size again.
    // The check above included only the space needed for local variables.
    // After genssa, the space needed includes local variables and the callee arg region.
    // We must do this check prior to calling pp.Flush.
    // If there are any oversized stack frames,
    // the assembler may emit inscrutable complaints about invalid instructions.
    if pp.Text.To.Offset >= maxStackSize {
        largeStackFramesMu.Lock()
        locals := f.Frontend().(*ssafn).stksize
        largeStackFrames = append(largeStackFrames, largeStack{locals: locals, args: f.OwnAux.ArgWidth(), callee: pp.Text.To.Offset - locals, pos: fn.Pos()})
        largeStackFramesMu.Unlock()
        return
    }

    pp.Flush() // assemble, fill in boilerplate, etc.
    // fieldtrack must be called after pp.Flush. See issue 20014.
    fieldtrack(pp.Text.From.Sym, fn.FieldTrack)
}

這裏貼出了 Compile 的完整實現,Compile 函數中真正負責生成具有 SSA 屬性的中間代碼的是 buildssa 函數,看了一下 buildssa 函數有近 300 行代碼,有點複雜,這裏挑挑揀揀,把主要的調用摘錄出來:

// $GOROOT/src/cmd/compile/internal/ssagen/ssa.go

// buildssa builds an SSA function for fn.
// worker indicates which of the backend workers is doing the processing.
func buildssa(fn *ir.Func, worker int) *ssa.Func {
    name := ir.FuncName(fn)
    ... ...

    // Convert the AST-based IR to the SSA-based IR
    s.stmtList(fn.Enter)
    s.zeroResults()
    s.paramsToHeap()
    s.stmtList(fn.Body)

    // fallthrough to exit
    if s.curBlock != nil {
        s.pushLine(fn.Endlineno)
        s.exit()
        s.popLine()
    }
    ... ...

    // Main call to ssa package to compile function
    ssa.Compile(s.f)
    ... ...
}

buildssa 中的 ssa.Compile 咱們後續再看,那個涉及到 SSA 的多輪 (pass) 優化,我們看一下從基於 AST 形式的 IR 到基於 SSA 形式的 IR 的轉換,無論是 fn.Enter 還是 fn.Body,本質都是一組 ir Node,stmtList 將這些 node 逐個轉換爲 SSA 形式。Go 提供了可視化的 ssa dump 工具,我們可以更直觀的來看一下。

Go 語言隸屬於命令式編程語言 (imperative programming language),這類編程範式有三大典型控制結構:順序結構、選擇結構和循環結構,我們先來看看一個最簡單的順序結構是如何翻譯爲 SSA 的:

// github.com/bigwhite/experiments/tree/master/ssa-examples/sequential.go

package main
  
func sum(a, b, c int) int {
    d := a + b
    e := d + c
    return e
}

func main() {
    println(sum(1, 2, 3))
}

我們通過下面命令來生成函數 sum 的 SSA 轉換過程:

$GOSSAFUNC=sum go build sequential.go 
dumped SSA to ./ssa.html
$mv ssa.html ssa-sequential.html
$open ./ssa-sequential.html

上面的 open 命令會在本地打開瀏覽器並顯示 ssa-sequential.html 頁面:

上圖中,最左側是源碼(源碼顯示兩次,感覺是 bug),中間的是 AST 形式的 IR,最右側的框框中就是 Go 編譯器生成的第一版 SSA,爲了更好說明,我們將其貼到下面來:

// github.com/bigwhite/experiments/tree/master/ssa-examples/ssa-sequential.html

b1:-
  v1 (?) = InitMem <mem>
  v2 (?) = SP <uintptr>
  v3 (?) = SB <uintptr>
  v4 (?) = LocalAddr <*int> {a} v2 v1
  v5 (?) = LocalAddr <*int> {b} v2 v1
  v6 (?) = LocalAddr <*int> {c} v2 v1
  v7 (?) = LocalAddr <*int> {~r0} v2 v1
  v8 (3) = Arg <int> {a} (a[int])
  v9 (3) = Arg <int> {b} (b[int])
  v10 (3) = Arg <int> {c} (c[int])
  v11 (?) = Const64 <int> [0]
  v12 (+4) = Add64 <int> v8 v9 (d[int])
  v13 (+5) = Add64 <int> v12 v10 (e[int])
  v14 (+6) = MakeResult <int,mem> v13 v1
Ret v14 (+6)

name a[int]: v8
name b[int]: v9
name c[int]: v10
name d[int]: v12
name e[int]: v13

從結構上來看,SSA 分爲兩部分,一部分是由 b1、Ret 組成的 blocks,另一部分則是命名變量與 SSA value 的對應關係。

在 SSA 中,一個 block 代表了一個函數控制流圖 (control flow graph) 中的基本代碼塊(basic block),從代碼註釋中可以看到 SSA 有四種 block 類型:Plain,If、Exit 和 Defer:

// $GOROOT/src/cmd/compile/internal/ssa/block.go

// BlockKind is the kind of SSA block.
//
//    kind          controls        successors
//  ------------------------------------------
//    Exit      [return mem]                []
//   Plain                []            [next]
//      If   [boolean Value]      [thenelse]
//   Defer             [mem]  [nopanic, panic]  (control opcode should be OpStaticCall to runtime.deferproc)
type BlockKind int16

但實際的 BlockKind 已經與註釋不一致了,opGen.go 是一個自動生成的文件,其中的 BlockKind 類型的常量值有數十個,即便濾掉 CPU 架構相關的常量,剩下的還有 8 個 (從 BlockPlain 到 BlockFirst):

// $GOROOT/src/cmd/compile/internal/ssa/opGen.go

const (
    BlockInvalid BlockKind = iota
    ... ...

    BlockPlain
    BlockIf
    BlockDefer
    BlockRet
    BlockRetJmp
    BlockExit
    BlockJumpTable
    BlockFirst
)

上面的 sum 函數的 SSA 代碼例子中,b1 應該就是 Plain 類型的,Ret 顯然是 BlockRet 類型。

Plain 類型的 Block 中是一組 values,value 是 SSA 的基本構成要素。根據 SSA 的定義,一個 value 只能被精確地定義一次,但是它可以被使用任意多次。如示例,一個 value 主要包括一個唯一的標識符,一個操作符,一個類型和一些參數,下面的 Value 類型的 LongString 和 LongHTML 方法返回的字符串更能說明 Value 的格式。尤其是 LongHTML 方法就是輸出 ssa html 中內容的方法:

// $GOROOT/src/cmd/compile/internal/ssa/value.go

// long form print.  v# = opcode <type> [aux] args [: reg] (names)
func (v *Value) LongString() string {
    ... ...
}

// $GOROOT/src/cmd/compile/internal/ssa/html.go
func (v *Value) LongHTML() string {
    // TODO: Any intra-value formatting?
    // I'm wary of adding too much visual noise,
    // but a little bit might be valuable.
    // We already have visual noise in the form of punctuation
    // maybe we could replace some of that with formatting.
    s := fmt.Sprintf("<span class=\"%s ssa-long-value\">", v.String())

    linenumber := "<span class=\"no-line-number\">(?)</span>"
    if v.Pos.IsKnown() {
        linenumber = fmt.Sprintf("<span class=\"l%v line-number\">(%s)</span>", v.Pos.LineNumber(), v.Pos.LineNumberHTML())
    }

    s += fmt.Sprintf("%s %s = %s", v.HTML(), linenumber, v.Op.String())

    s += " <" + html.EscapeString(v.Type.String()) + ">"
    s += html.EscapeString(v.auxString())
    for _, a := range v.Args {
        s += fmt.Sprintf(" %s", a.HTML())
    }
    r := v.Block.Func.RegAlloc
    if int(v.ID) < len(r) && r[v.ID] != nil {
        s += " : " + html.EscapeString(r[v.ID].String())
    }
    var names []string
    for name, values := range v.Block.Func.NamedValues {
        for _, value := range values {
            if value == v {
                names = append(names, name.String())
                break // drop duplicates.
            }
        }
    }
    if len(names) != 0 {
        s += " (" + strings.Join(names, ", ") + ")"
    }

    s += "</span>"
    return s
}

以例子中的 v12 這一個 value 爲例:

  v12 (+4) = Add64 <int> v8 v9 (d[int])

ssa dump 輸出的另一部分則是命名變量與 SSA value 的對應關係,其格式也是:name LocalSlot: value:

name a[int]: v8
name b[int]: v9
name c[int]: v10
name d[int]: v12
name e[int]: v13

輸出上述第二部分的代碼如下:

// $GOROOT/src/cmd/compile/internal/ssa/print.go
func (p stringFuncPrinter) named(n LocalSlot, vals []*Value) {
    fmt.Fprintf(p.w, "name %s: %v\n", n, vals)
}

順序結構的代碼執行流是從上到下的,每個 block 後面僅有一個後繼 block,這樣的 SSA 轉換較爲好理解。

下面我們再來看看一個選擇控制結構 - if 控制語句的 ssa,下面是我們的示例 Go 源碼:

// github.com/bigwhite/experiments/tree/master/ssa-examples/selection_if.go

package main

func foo(b bool) int {
    if b {
        return 2
    }
    return 3
}

func main() {
    println(foo(true))
}

我們通過下面命令輸出函數 foo 的 SSA 中間代碼:

$GOSSAFUNC=foo go build selection_if.go 
dumped SSA to ./ssa.html
$mv ssa.html ssa-selection-if.html
$open ./ssa-selection-if.html

open 命令啓動瀏覽器顯示 foo 函數的 SSA 形式:

有了上面關 Go SSA 格式的基礎,這段 SSA 代碼分析起來就容易一些了。

這段 SSA 中有多個 block,包括 plain block、if block、ret block 等。我們重點關注 SSA 對 if 語句的處理。

經典 SSA 轉換理論中,SSA 將 if 分支轉換爲帶有Φ函數的 SSA 代碼 (如下圖):

圖:if 語句的 SSA 轉換 (圖來自 keith 博士的 slide)

Φ函數 (希臘字母 fài) 是代碼中的一個 merge point,它可以將其前置的 n 個 block 的執行路徑匯聚在一起。不過它僅用於代碼分析使用,最終生成的代碼中並不會有Φ函數的存在。關於在何處插入Φ函數等算法太理論了,這裏就不展開了。

我們看看現實中 go 針對 if 語句的處理:

b1:
  v1 (?) = InitMem <mem>
  v2 (?) = SP <uintptr>
  v3 (?) = SB <uintptr>
  v4 (?) = LocalAddr <*bool> {b} v2 v1
  v5 (?) = LocalAddr <*int> {~r0} v2 v1
  v6 (3) = Arg <bool> {b} (b[bool])
  v7 (?) = Const64 <int> [0]
  v8 (?) = Const64 <int> [2]
  v11 (?) = Const64 <int> [3]
If v6 → b3 b2 (4)

b2: ← b1
  v13 (7) = Copy <mem> v1
  v12 (7) = MakeResult <int,mem> v11 v13
Ret v12 (+7)

b3: ← b1
  v10 (5) = Copy <mem> v1
  v9 (5) = MakeResult <int,mem> v8 v10
Ret v9 (+5)

name b[bool]: v6

這裏關鍵是 if block,if 判斷 v6 即變量 b 的值,如果爲 true,代碼執行就流向 block b3,否則流向 block b2。

下面的 b2、b3 block 也都包含了前置 block 的屬性,以 b2 爲例,對於來自 b1 block 的流,執行對應 block 的代碼。基於 switch 的選擇語句更爲複雜,有興趣的朋友可以自己看一下 ssa-selection-switch.html。

我們最後看一下循環結構,下面是 Go 代碼:

// github.com/bigwhite/experiments/tree/master/ssa-examples/for_loop.go
package main

func sumN(n int) int {
 var r int
 for i := 1; i <= n; i++ {
  r = r + i
 }
 return r
}

func main() {
 println(sumN(10))
}

其生成的 SSA 如下圖:

我們看到循環結構的 ssa block 更多,流向更爲複雜,如果將其轉換爲一張圖的話,那就應該是這樣的:

我們看到:無論是選擇結構還是循環結構,SSA 實質上構建了一個函數的控制流圖 (control flow graph),圖中每個節點就是一個 block,函數的執行控制流在各個 block 間轉移。而後續基於 SSA 的優化就是基於 block 中 value 的僅賦值一次的特性以及 block 的控制流圖進行的

接下來,我們簡單看看目前 Go 基於 SSA IR 都做了哪些優化。

4. 基於 SSA 的多輪 (pass) 優化

buildssa 函數中 ssa.Compile 調用執行了基於 SSA IR 的多輪 (passes) 優化:

// $GOROOT/src/cmd/compile/internal/ssa/compile.go

func Compile(f *Func) {
    ... ...
    for _, p := range passes {
        ... ...
        tStart := time.Now()
        p.fn(f)
        tEnd := time.Now()
        ... ...
    }
}

我們看到,針對某個函數,Compile 函數對其安裝預置的 passes 進行多輪優化,都有哪些 pass 呢?我們來看看:

// $GOROOT/src/cmd/compile/internal/ssa/compile.go

// list of passes for the compiler
var passes = [...]pass{
    {name: "number lines", fn: numberLines, required: true},
    {name: "early phielim", fn: phielim},
    {name: "early copyelim", fn: copyelim},
    {name: "early deadcode", fn: deadcode}, // remove generated dead code to avoid doing pointless work during opt
    {name: "short circuit", fn: shortcircuit},
    {name: "decompose user", fn: decomposeUser, required: true},
    {name: "pre-opt deadcode", fn: deadcode},
    ... ...
    {name: "regalloc", fn: regalloc, required: true},   // allocate int & float registers + stack slots
    {name: "loop rotate", fn: loopRotate},
    {name: "stackframe", fn: stackframe, required: true},
    {name: "trim", fn: trim}, // remove empty blocks
}

粗略數了一下,這裏約有 50 個 pass(其中包含多輪的 deadcode 清理),每個 pass 執行的代碼都位於 $GOROOT/src/cmd/compile/internal/ssa 目錄下,我們也可以通過 dump 出的 html 查看每一 pass 後得到的 SSA 結果,以 ssa-sequential.html 爲例,其多輪優化的示意圖如下:

點擊瀏覽器頁面上的黑體字優化標題 (比如:lowered deadcode for cse),這一步產生的 SSA 代碼都會顯示出來,最後一個框框中是基於 SSA 生成目標架構的彙編代碼。

每一個 pass 都有其獨特性,比如 cse,代表 Common Subexpression Elimination(共同子表達式刪除) ,下面是一個 cse 優化的例子:

y = x + 5
...
z = x + 5

cse 優化後 (前提中間過程中 x 值沒變過):

y = x + 5
...
z = y

在這個示例中,經過一輪 cse,Go 便可以節省下一次沒必要的加法運算 (z = x + 5)。別看一次加法運算不起眼,積累多了也是不小的性能提升,如果你對某一 pass 的優化動作感興趣,可以對照 $GOROOT/src/cmd/compile/internal/ssa 目錄下的代碼與瀏覽器中生成的 SSA 來對其進行深入研究。

5. 小結

編譯器後端的邏輯總是很難理解的,本文對 Go 編譯器與 SSA 的淵源、Go 編譯器中驅動 SSA 轉換和優化的環節以及 Go 生成的 SSA 的形式與過程做了介紹,算是對 SSA 入了個門。但要想真正搞懂 SSA 轉換以及基於 SSA 的優化步驟的細節,認真閱讀 SSA 相關的 paper 和資料 (見參考資料) 以及相關 code 是不可或缺的。

本文涉及的代碼在這裏 [11] 可以下載 - https://github.com/bigwhite/experiments/tree/master/ssa-examples

6. 參考資料


Gopher Daily(Gopher 每日新聞) 歸檔倉庫 - https://github.com/bigwhite/gopherdaily

我的聯繫方式:

商務合作方式:撰稿、出書、培訓、在線課程、合夥創業、諮詢、廣告合作。

參考資料

[1] 

《通過實例理解 Go 內聯優化》: https://tonybai.com/2022/10/17/understand-go-inlining-optimisations-by-example

[2] 

龍書《編譯原理 (第二版)》: https://book.douban.com/subject/3296317/

[3] 

構建 SSA 的快速算法: https://www.cs.utexas.edu/~pingali/CS380C/2010/papers/ssaCytron.pdf

[4] 

Go 編譯器還相對年輕: https://tonybai.com/2021/11/11/go-opensource-12-years

[5] 

Go 1.5 版本: https://tonybai.com/2015/07/10/some-changes-in-go-1-5/

[6] 

golang-dev google group: https://groups.google.com/g/golang-dev

[7] 

“New SSA Backend for the Go Compiler”: https://docs.google.com/document/d/1szwabPJJc4J-igUZU4ZKprOrNRNJug2JPD8OYi3i1K0/edit

[8] 

Go 1.7 版本: https://tonybai.com/2016/06/21/some-changes-in-go-1-7

[9] 

Go 程序的性能因對 SSA 的支持而提升 5%-35% 以上: https://go.dev/doc/go1.7

[10] 

Go 1.8 版本: https://tonybai.com/2017/02/03/some-changes-in-go-1-8/

[11] 

這裏: https://github.com/bigwhite/experiments/tree/master/ssa-examples

[12] 

“Gopher 部落” 知識星球: https://wx.zsxq.com/dweb2/index/group/51284458844544

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/ZPRIT2NJp7-P5n8gQ4zKvg