Go1.25 新特性:支持 FlightRecorder 模式、支持讀取符號鏈接、OS 版本推進等
大家好,我是煎魚。
最近 Go 在前幾天已經發布了 Go1.25 的 RC1 版本:
圖片
這次還是比較準時的。之前 Go1.25 新特性我們也介紹了不少,今天分享一些零碎但偶爾可能需要關注的新特性點。
Go 所要求的 OS 版本推進
每次新版本迭代,都會迎來 OS 系統版本的進一步的新老推進,是我們需要及時關注的。為此之前還推進了測試老集群的版本升級。
圖片
- MacOS 系統:從 Go1.25 版本起,需要 macOS 12 Monterey 或更高版本。對以前版本的支持已經停止。
- Windows 系統:Go1.25 是最后一個包含對 32 位 Windows ARM 架構(GOOS=windows,GOARCH=arm) 支持的版本,這個移植版本存在問題(被稱為“broken”)。
- 從 Go1.26 開始,這個架構支持將被移除,即不再支持在 32 位的 Windows ARM 系統上編譯或運行 Go 程序。
- Linux 系統:內核方面沒有新的變化要求。
Panic 輸出日志更簡潔
當程序因為一個未處理的 panic
(已被 recover
后又重新 panic
)而退出時,打印的信息現在不會再重復 panic
值的文本內容。
之前如果一個程序執行了 panic("PANIC")
,然后通過 recover
捕獲了這個 panic
,接著又使用原始值重新 panic
,他會打印:
panic: PANIC [recovered]
panic: PANIC
現在,程序將改為打印:
panic: PANIC [recovered, repanicked]
本次 Go1.25 主要就是將兩次 panic
輸出的語義信息合并到一行中,并避免重復顯示 panic
值 "PANIC",讓日志更簡潔清晰。
ReadLinkFS、DirFS 支持獲取符號鏈接的指向位置
在 Go1.25 中,引入了一個新的接口,稱為 ReadLinkFS
。這個接口提供了讀取文件系統中符號鏈接(symbolic links)的能力:
圖片
ReadLinkFS
對應的接口如下:
type ReadLinkFS interface {
FS
// ReadLink returns the destination of the named symbolic link.
// If there is an error, it should be of type [*PathError].
ReadLink(name string) (string, error)
// Lstat returns a [FileInfo] describing the named file.
// If the file is a symbolic link, the returned [FileInfo] describes the symbolic link.
// Lstat makes no attempt to follow the link.
// If there is an error, it should be of type [*PathError].
Lstat(name string) (FileInfo, error)
}
ReadLinkFS
接口允許程序讀取符號鏈接的目標路徑。這意味著開發者可以通過該接口輕松地獲取符號鏈接指向的實際文件或目錄位置。
同時還包括對 os.DirFS
的改進,使其實現了 ReadLinkFS
接口:
圖片
這意味著在使用 os.DirFS
時,用戶也可以利用該接口來處理符號鏈接。
TypeAssert 支持對值進行斷言
在日常 Go 程序進行反射時,我們一般是使用 reflect.ValueOf
方法來進行。但是經過性能測試,發現了一些問題。
基準測試代碼:
func Benchmark(b *testing.B) {
// 通過反射創建一個可尋址的 time.Time 值
v := reflect.ValueOf(new(time.Time)).Elem()
// // 開啟內存分配報告
b.ReportAllocs()
for i := 0; i < b.N; i++ {
// 將反射值轉為接口,再轉回 time.Time
_ = v.Interface().(time.Time)
}
}
這段基準測試的目標是測量 v.Interface().(time.Time)
操作的性能和內存開銷。
測試結果:
Benchmark-24 37673833 29.74 ns/op 24 B/op 1 allocs/op
需要重點關注的是:每次操作中發生了 1 次內存分配。
會有內存分配的原因是:
- 由于
v
是一個可尋址的time.Time
值(通過Elem()
獲取),Go 的reflect.Value.Interface()
會復制這個值。 - 復制的目的是:為了防止外部代碼通過接口訪問時直接修改原始數據(因為原始值是可尋址的,為了保持封裝性和安全性)。
- 這個復制過程會導致 一次堆內存分配(alloc),從而出現了
24 B/op
和 1allocs/op
。
在前幾年 tailscale 的 @Joe Tsai 大佬發現了這個問題,并提出了以下提案:
圖片
當時提案上希望增加 TypeAssert
的泛型方法:
// AssertTo is semantically equivalent to:
// v2, ok := v.Interface().(T)
func AssertTo[T any](v reflect.Value "T any") (T, bool)
新增該方法后,其作用語義上等同于 v.Interface().(T)
,但有效避免了 interface
裝箱帶來的復制和堆分配。
從而在性能關鍵路徑(例如:反射密集的場景)提供了更高效的替代方案。
后續的用法如下:
t, ok := reflect.AssertTo[time.Time](v "time.Time")
...
當然,最后到 Go1.25 函數的名字改了,改成以下函數簽名:
func TypeAssert[T any](v Value "T any") (T, bool)
改名的原因是 rsc 認為:簡單的 Assert 可能會與 C 風格的斷言混淆。另外改名后有了 Type 前綴,也就不需要 To 后綴了。
trace 支持 FlightRecorder 模式
FlightRecorder 是什么
“Flight recording”(飛行記錄)是一種技術,它將追蹤數據保存在一個概念性的環形緩沖區中,并在需要時將其刷新。
該技術的目的是捕獲程序中有趣行為的追蹤數據,即使事先無法預測這些行為會在什么時候發生。
例如:
- 當 Web 服務未通過健康檢查,或者處理某個請求的時間異常過長時。
具體來說,Web 服務可以在這些異常實際發生時檢測到,但程序員在部署時卻無法準確預知它們會在何時出現。
而在異常發生之后再啟動追蹤往往并沒有什么用,因為此時程序已經執行過了那些“有趣”的部分。
Go FlightRecorder 落地
Go 在 runtime 中能夠做 FlightRecorder,還是得益于之前在以下 issues#60773 對 trace 做過大修和性能優化:
圖片
再同樣由 @Michael Knyszek 提起以下 FlightRecorder 的正式提案和實現內容:
圖片
在 Go 中,所有追蹤數據將被組織為一系列自包含的分區(partitions)。這一實現上的變更為 Go 的執行追蹤器添加類似“飛行記錄”的能力提供了機會 —— 即始終保留至少一個分區,可在任意時刻快照保存。
而正因為 issues#60773 的優化,Go trace 的性能有了大幅優化。長時間追蹤下啟用 FlightRecorder 已經足夠輕量,不再是一個性能負擔。從而在這個版本總算是實現了。
以下是 runtime/trace
包中的新 API,用于啟用 FlightRecorder。大家可以自行在 Go 程序中進行觸發。
如下 API 代碼:
package trace
type FlightRecorder struct {
...
}
func (*FlightRecorder) SetMinAge(d time.Duration)
func (*FlightRecorder) MinAge() time.Duration
func (*FlightRecorder) SetMaxBytes(bytes uint64)
func (*FlightRecorder) MaxBytes() uint64
func (*FlightRecorder) Start() error
func (*FlightRecorder) Stop() error
func (*FlightRecorder) Enabled() bool
func (*FlightRecorder) WriteTo(w io.Writer) (n int64, err error)
完整的 API 文檔可以到 runtime/trace@master#FlightRecorder[1] 進行查看。
總結
本次主要是針對一些細致的特性進行了說明,像是大家可以檢查自己 macOS 是否符合新版本的訴求。之前我就有因此將 OS 和集群版本升級的訴求。
另外本次的 runtime/trace 的 FlightRecorder 模式支持將帶來更多樣的調試追蹤的提效,反射中 TypeAssert 新方法支持帶來的的性能優化、ReadLinkFS 和 DirFS 支持符合鏈接(軟鏈)的讀取,都是一些有實際效益的優化點。
參考資料
[1] runtime/trace@master#FlightRecorder: https://pkg.go.dev/runtime/trace@master#FlightRecorder