Go1.19 那些事:國產晶片、記憶體模型等新特性,你知道多少?

語言: CN / TW / HK

大家好,我是煎魚。

感覺時間過得很快,Go1.18 釋出沒太久,泛型還在風風火火,看了看上次的投票結果,絕大部分同學還沒有在生產環境應用泛型。

這不,Go1.19 Beta1 已經正式釋出了。今天就由煎魚和大家圍觀《Go 1.19 Release Notes》中一些有意思的特性。

記憶體模型

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 寫了 Go Memory Model 的三篇文章作為系列說明: - 《Hardware Memory Models》 - 《Programming Language Memory Models》 - 《Updating the Go Memory Model

有興趣的同學可以閱讀。

文件規範

Russ Cox 在提案《Proposal: go/doc: headings, lists, and links in Go doc comments》中,增加了對文件註釋中的連結、列表和更清晰的標題的支援。

Go 1.19 文件已經發生了變化。如下:

舊(左)與新(右)的對比圖。

手動貼連結變可跳轉:

手動分行變成無序列表區分:

這算是 Go 文件從遠古時代到新 Markdown 的一個大升級了。

構建約束

從 Go1.19 起,構建約束 unix 現在可以在 //go:build行中被識別,能夠起到配套的約束作用。

如下格式:

shell //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 來實現大型整數和字串型別的 swicth 語句。switch 語句的效能改進各不相同,但可以快 20% 左右。

注:本次僅涉及 GOARCH=amd64GOARCH=arm64 的變更。

執行時

堆記憶體限制

新版本的 Go 增加了 runtime.SetMemoryLimit 函式和 GOMEMLIMIT 環境變數。

關注到 runtime.SetMemoryLimit 函式為執行時提供了一個記憶體的軟限制。

函式簽名為:

go 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》,涉及到的是對方法宣告中型別引數的範圍做了一個非常小的修正。

原有描述:

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 時,以下泛型程式碼會提示錯誤:

```go type T[T any] struct {}

func (T[T]) m() {} // error: T is not a generic type ```

在新版本(1.19 起)將會正確支援,不會發生編譯錯誤。

其他的泛型進度來講,還是在修修補補:

有待繼續觀察。

總結

在本次 Go1.19 的新版本更新中,新特性是比較少的。其中主要的原因還是泛型的各項工作給 Go 團隊帶來了不少的工作量。

今年也陸續有個別大佬離開,所以整體可用於其他新特性的時間就比較少了。

這個版本可以認為是小版本,填了一些小 “坑” 了,國內個別面試題的答案也會因此有所改變了。

文章持續更新,可以微信搜【腦子進煎魚了】閱讀,本文 GitHub github.com/eddycjy/blog 已收錄,學習 Go 語言可以看 Go 學習地圖和路線,歡迎 Star 催更。

Go 圖書系列