Go1.19 那些事:國產芯片、內存模型等新特性,你知道多少?
大家好,我是煎魚。
感覺時間過得很快,Go1.18 發布沒太久,泛型還在風風火火,看了看上次的投票結果,絕大部分同學還沒有在生產環境應用泛型。
這不,Go1.19 Beta1 已經正式發布了。今天就由煎魚和大家圍觀《Go 1.19 Release Notes[1]》中一些有意思的特性。
內存模型
Go 的內存模型已被修訂,以使 Go 與 C、C++、Java、JavaScript、Rust 和 Swift 使用的內存模型保持一致。Go 只提供順序一致的原子學,而不是其他語言中的任何更寬松的形式。
另外隨著內存模型的更新,Go1.19 在 sync/atomic 包中引入了新的類型,使之更容易使用原子值,如 atomic.Int64 和 atomic.Pointer[T]。
文檔做了以下具體的修改:
- 記錄 Go 的整體內存模型描述。
- 記錄 multiword 競態會導致崩潰的情況。
- 記錄 runtime.SetFinalizer 的 happens-before。
- 記錄(或鏈接)更多同步類型的發生前。
- 記錄同步/原子的發生時間,匹配 C++ 的順序一致的原子(以及Java、JavaScript、Rust、Swift、C...)。
- 記錄不允許的編譯器優化。
這個只是 “修訂”,是改了文檔和定義,并不涉及內存模型的代碼變更。
文檔規范
Russ Cox 在提案《Proposal: go/doc: headings, lists, and links in Go doc comments[5]》中,增加了對文檔注釋中的鏈接、列表和更清晰的標題的支持。
Go 1.19 文檔已經發生了變化。如下:
舊(左)與新(右)的對比圖。
手動貼鏈接變可跳轉:
手動分行變成無序列表區分:
這算是 Go 文檔從遠古時代到新 Markdown 的一個大升級了。
構建約束
從 Go1.19 起,構建約束 unix 現在可以在 //go:build 行中被識別,能夠起到配套的約束作用。
如下格式:
//go:build unix
需要注意的是,在 1.19 版本中,如果 GOOS 是 aix、android、darwin、dragonfly、freebsd、hurd、illumos、ios、linux、netbsd、openbsd 或 solaris 中的一種,也是滿足 unix 約束的。
龍芯架構
龍芯(Loongson)是由中國科學院計算技術研究所、龍芯中科、神州龍芯等機構、公司所設計的一系列各種芯片(包括通用中央處理器、SoC、微控制器、芯片組等)。
在 Go 1.19 起增加了對 Linux 上 Loongson 64 位架構的支持(GOOS=linux,GOARCH=loong64)。
前段時間還看到龍芯中科,在科創板上市,成國產 CPU 第一股。國產芯片走進 Go 語言,應該也是國人推進的,太強了!
競態檢測
Go 的競態資源檢測(race detector)已經發布到 v3 版本了,將會跟隨 Go1.19 一起上線到生產可用。
與 v2 版相比,新版本的 race detector 在性能上快 1.5 倍到 2 倍,使用一半的內存,并且支持無限數量的 goroutine。
注:windows/amd64 和 openbsd/amd64 暫未支持。
Switch 性能提高
Go 編譯器現在使用 jump table[6] 來實現大型整數和字符串類型的 swicth 語句。switch 語句的性能改進各不相同,但可以快 20% 左右。
注:本次僅涉及 GOARCH=amd64 和 GOARCH=arm64 的變更。
運行時
堆內存限制
新版本的 Go 增加了 runtime.SetMemoryLimit 函數和 GOMEMLIMIT 環境變量。
關注到 runtime.SetMemoryLimit 函數為運行時提供了一個內存的軟限制。
函數簽名為:
func SetMemoryLimit(limit int64) int64
有了這個內存的軟限制后,Go 運行時將會遵守這個內存限制,行為包括:調整垃圾回收的頻率、更積極地將內存返回到底層系統等,來維持這個軟內存的限制。
另外即使 GOGC=off(或者是執行了 SetGCPercent(-1) 函數),也會遵守軟內存的限制。
有了內存軟限制,一般場景下,可以有效的防止由于堆內存分配過多,導致 Go 進程超出系統內存資源的最大被 KILL 的場景。
一個漏網之魚,是限制不了的。那就是它不包括:Go 二進制使用的空間和 Go 外部的內存,例如:由底層系統代表進程管理的內存,或由同一進程中的非 Go 代碼管理的內存(CGO)。
Goroutine 堆棧
新版本中 Go 運行時將根據 goroutine 的歷史平均堆棧使用率來分配初始 goroutine 堆棧(大霧,太壞了,Go 面試題的題目答案又要改了...)。
可以有效避免一些不必要的堆棧增長和復制,在低于平均水平的情況下,能節省最多 2 倍的空間浪費。
這是一個比較細致的優化點了。
泛型改進
Go1.19 還在不斷地完善泛型的路上,這次變更來自規范《spec: adjust scope of type parameters declared by method receivers[7]》,涉及到的是對方法聲明中類型參數的范圍做了一個非常小的修正。
原有描述:
The scope of an identifier denoting a type parameter of a function or declared by a method receiver is the function body and all parameter lists of the function.
修訂描述:
The scope of an identifier denoting a type parameter of a function or declared by a method receiver starts after the function name and ends at the end of the function body.
在 Go1.18 時,以下泛型代碼會提示錯誤:
type T[T any] struct {}
func (T[T]) m() {} // error: T is not a generic type
在新版本(1.19 起)將會正確支持,不會發生編譯錯誤。
其他的泛型進度來講,還是在修修補補:
有待繼續觀察。
總結
在本次 Go1.19 的新版本更新中,新特性是比較少的。其中主要的原因還是泛型的各項工作給 Go 團隊帶來了不少的工作量。
今年也陸續有個別大佬離開,所以整體可用于其他新特性的時間就比較少了。
這個版本可以認為是小版本,填了一些小 “坑” 了,國內個別面試題的答案也會因此有所改變了。
參考資料
[1]Go 1.19 Release Notes: https://tip.golang.org/doc/go1.19
[2]Hardware Memory Models: https://research.swtch.com/hwmm
[3]Programming Language Memory Models: https://research.swtch.com/plmm
[4]Updating the Go Memory Model: https://research.swtch.com/gomm
[5]Proposal: go/doc: headings, lists, and links in Go doc comments: https://github.com/golang/proposal/blob/master/design/51082-godocfmt.md
[6]jump table: https://en.wikipedia.org/wiki/Branch_table
[7]spec: adjust scope of type parameters declared by method receivers: https://github.com/golang/go/issues/52038