Go 1.20新特性前瞻

2023-10-24 03:20
文章标签 go 特性 前瞻 1.20

本文主要是介绍Go 1.20新特性前瞻,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

在近期Russ Cox代表Go核心团队发表的“Go, 13周年”[1]一文中,他提到了“在Go的第14个年头,Go团队将继续努力使Go成为用于大规模软件工程的最好的环境,将特别关注供应链安全,提高兼容性和结构化日志记录,当然还会有很多其他改进,包括profile-guided optimization等”。

当前正在开发的版本是Go 1.20,预计2023年2月正式发布,这个版本也将是Go在其第14个年头发布的第一个版本。很多人没想到Go真的会进入到Go 1.2x版本,而不是Go 2.x。记得Russ Cox曾说过可能永远也不会有Go2了,毕竟Go泛型语法落地[2]这么大的语法改动也没有让Go1兼容性承诺[3]失效。

目前Go 1.20版本正在如火如荼的开发中,很多gopher都好奇Go 1.20版本会带来哪些新特性?在这篇文章中,我就带大家一起去Go 1.20 milestone[4]的issues列表中翻翻,提前看看究竟会有哪些新特性加入Go。

1. 语法变化

Go在其1.18版本[5]迎来了自开源以来最大规模的语法变化,然后呢?就没有然后了。Go在语法演进上再次陷入沉寂,没错,这就是Go长期以来坚持的风格。

如果Go 1.20版本真有语法层面的变化,那估计就是这个issue了:“spec: allow conversion from slice to array”[6],即允许切片类型到数组类型的类型转换

在Go 1.20版本之前,我们以Go 1.19版本[7]为例写下下面代码:

package mainimport "fmt"func main() {var sl = []int{1, 2, 3, 4, 5, 6, 7}var arr = [7]int(sl) // 编译器报错:cannot convert sl (variable of type []int) to type [7]intfmt.Println(sl)fmt.Println(arr)
}

这段代码中,我们进行了一个[]int到[7]int的类型转换,但在Go 1.19版本编译器针对这个转换会报错!即不支持将切片类型显式转换数组类型。

在Go 1.20版本之前如果要实现切片到数组的转换,是有trick的,看下面代码:

func main() {var sl = []int{1, 2, 3, 4, 5, 6, 7}var parr = (*[7]int)(sl)var arr = *(*[7]int)(sl)fmt.Println(sl)  // [1 2 3 4 5 6 7]fmt.Println(arr) // [1 2 3 4 5 6 7]sl[0] = 11fmt.Println(sl)    // [11 2 3 4 5 6 7]fmt.Println(arr)   // [1 2 3 4 5 6 7]fmt.Println(*parr) // [11 2 3 4 5 6 7]
}

该trick的理论基础是Go允许获取切片的底层数组地址。在上面的例子中parr就是指向切片sl底层数组的指针,通过sl或parr对底层数组元素的修改都能在对方身上体现出来。但是arr则是底层数组的一个副本,后续通过sl对切片的修改或通过parr对底层数组的修改都不会影响arr,反之亦然。

不过这种trick语法还不是那么直观!于是上面那个“允许将切片直接转换为数组”的issue便提了出来。我们在go playground[8]上选择“go dev branch”便可以使用最新go tip的代码,我们尝试一下最新语法:

func main() {var sl = []int{1, 2, 3, 4, 5, 6, 7}var arr = [7]int(sl)      var parr = (*[7]int)(sl)fmt.Println(sl)   // [1 2 3 4 5 6 7]fmt.Println(arr)  // [1 2 3 4 5 6 7]sl[0] = 11fmt.Println(arr)  // [1 2 3 4 5 6 7]fmt.Println(parr) // &[11 2 3 4 5 6 7]
}

我们看到直接将sl转换为数组arr不再报错,但其语义与前面的“var arr = ([7]int)(sl)”语义是相同的,即返回一个切片底层数组的副本,arr不会受到后续切片元素变化的影响。

不过这里也有个约束,那就是转换后的数组长度要小于等于切片长度,否则会panic:

var sl = []int{1, 2, 3, 4, 5, 6, 7}
var arr = [8]int(sl) // panic: runtime error: cannot convert slice with length 7 to array or pointer to array with length 8

在写本文时,该issue尚未close,不过进入最终Go 1.20版本应该不是大问题。

2. 编译器/链接器和其他工具链

1) profile-guided optimization

Go编译器团队一直致力于对Go编译器/链接器的优化,这次在Go 1.20版本中,该团队很大可能会给我们带来“profile-guided optimization”[9]

什么是“profile-guided optimization”呢?原先Go编译器实施的优化手段,比如内联[10],都是基于固定规则决策的,所有信息都来自编译的Go源码。而这次的“profile-guided optimization”[11]顾名思义,需要源码之外的信息做“制导”来决定实施哪些优化,这个源码之外的信息就是profile信息,即来自pprof工具在程序运行时采集的数据,如下图(图来自profile-guided optimization设计文档[12])所示:

outside_default.png

因此pgo优化实际上是需要程序员参与的,程序员拿着程序到生产环境跑,程序生成的profile性能采集数据会被保存下来,然后这些profile采集数据会提供给Go编译器,以在下次构建同一个程序时辅助优化决策。由于这些profile是来自生产环境或模拟生产环境的数据,使得这种优化更有针对性。并且,Google数据中心其他语言(C/C++)实施PGO优化的效果显示,优化后的性能保守估计提升幅度在5%~15%。

和其他新引入的特性一样,Go 1.20将包含该特性,但默认并不开启,我们可以手动开启进行体验,未来版本,pgo特性才会默认为auto开启。

2) 大幅减小Go发行版包的Size

随着Go语言的演进,Go发行版的Size也在不断增加,从最初的几十M到如今的上百M。本地电脑里多安装几个Go版本,(解压后)几个G就没有了,此外Size大也让下载时间变得更长,尤其是一些网络环境不好的地区。

为什么Go发行版Size越来越大呢?这很大程度是因为Go发行版中包含了GOROOT下所有软件包的预编译.a文件,以go 1.19的macos版本为例,在$GOROOT/pkg下,我们看到下面这些.a文件,用du查看一下占用的磁盘空间,达111M:

$ls
archive/ database/ fmt.a  index/  mime/  plugin.a strconv.a time/
bufio.a  debug/  go/  internal/ mime.a  reflect/ strings.a time.a
bytes.a  embed.a  hash/  io/  net/  reflect.a sync/  unicode/
compress/ encoding/ hash.a  io.a  net.a  regexp/  sync.a  unicode.a
container/ encoding.a html/  log/  os/  regexp.a syscall.a vendor/
context.a errors.a html.a  log.a  os.a  runtime/ testing/
crypto/  expvar.a image/  math/  path/  runtime.a testing.a
crypto.a flag.a  image.a  math.a  path.a  sort.a  text/$du -sh
111M .

而整个pkg目录的size为341M,占Go 1.19版本总大小495M的近70%。

于是在Go社区提议下,Go团队决定从Go 1.20开始发行版不再为GOROOT中的大多数软件包提供预编译的.a文件[13],新版本将只包括GOROOT中使用cgo的几个软件包的.a文件。

因此Go 1.20版本中,GOROOT下的源码将像其他用户包那样在构建后被缓存到本机cache中。此外,go install也不会为GOROOT软件包安装.a文件,除非是那些使用cgo的软件包。这样Go发行版的size将最多减少三分之二。

取而代之的是,这些包将在需要时被构建并缓存在构建缓存中,就像已经为GOROOT之外的非主包所做的那样。此外,go install也不会为GOROOT软件包安装.a文件,除非是那些使用cgo的软件包。这些改变是为了减少Go发行版的大小,在某些情况下可以减少三分之二。

3) 扩展代码覆盖率(coverage)报告到应用本身

想必大家都用过go test的输出过代码覆盖率,go test会在unit test代码中注入代码以统计unit test覆盖的被测试包路径,下面是代码注入的举例:

func ABC(x int) {if x < 0 {bar()}
}

注入代码后:

func ABC(x int) {GoCover_0_343662613637653164643337.Count[9] = 1;if x < 0 {GoCover_0_343662613637653164643337.Count[10] = 1;bar()}
}

像GoCover_xxx这样的代码会被放置到每条分支路径下。

不过go test -cover也有一个问题,那就是它只是适合针对包收集数据并提供报告,它无法针对应用整体给出代码覆盖度报告。

Go 1.20版本中有关的“extend code coverage testing to include applications”[14]的proposal就是来扩展代码覆盖率的,可以支持对应用整体的覆盖率统计和报告。

该特性在Go 1.20版本中也将作为实验性特性,默认是off的。该方案通过go build -cover方式生成注入了覆盖率统计代码的应用程序,在应用执行过程中,报告会被生成到指定目录下,我们依然可以通过go tool cover来查看这个整体性报告。

此外,新proposal在实现原理上与go test -cover差不多,都是source-to-source的方案,这样后续也可以统一维护。当然Go编译器也会有一些改动。

4) 废弃-i flag

这个是一个早计划好的“废弃动作”[15]。自从Go 1.10引入go build cache后,go build/install/test -i就不会再将编译好的包安装到$GOPATH/pkg下面了。

3. Go标准库

1) 支持wrap multiple errors

Go 1.20增加了一种将多个error包装(wrap)为一个error的机制[16],方便从打包后的错误的Error方法中一次性得到包含一系列关于该错误的相关错误的信息。

这个机制增加了一个(匿名)接口和一个函数:

interface {Unwrap() []error
}func Join(errs ...error) error

同时增强了像fmt.Errorf这样的函数的语义,当在Errorf中使用多个%w verb时,比如:

e := errors.Errorf("%w, %w, %w", e1, e2, e3)

Errorf将返回一个将e1, e2, e3打包完的且实现了上述带有Unwrap() []error方法的接口的错误类型实例。

Join函数的语义是将传入的所有err打包成一个错误类型实例,该实例同样实现了上述带有Unwrap() []error方法的接口,且该错误实例的类型的Error方法会返回换行符间隔的错误列表。

我们看一下下面这个例子:

package mainimport ("errors""fmt"
)type MyError struct {s string
}func (e *MyError) Error() string {return e.s
}func main() {e1 := errors.New("error1")e2 := errors.New("error2")e3 := errors.New("error3")e4 := &MyError{s: "error4",}e := fmt.Errorf("%w, %w, %w, %w", e1, e2, e3, e4)fmt.Printf("e = %s\n", e.Error()) // error1 error2, error3, error4fmt.Println(errors.Is(e, e1)) // truevar ne *MyErrorfmt.Println(errors.As(e, &ne)) // truefmt.Println(ne == e4) // true
}

我们首先在Go 1.19编译运行上面程序:

e = error1 %!w(*errors.errorString=&{error2}), %!w(*errors.errorString=&{error3}), %!w(*main.MyError=&{error4})
false
false
false

显然Go 1.19的fmt.Errorf函数尚不支持多%w verb。

而Go 1.20编译上面程序的运行结果为:

e = error1 error2, error3, error4
true
true
true

将fmt.Errorf一行换为:

e := errors.Join(e1, e2, e3, e4)

再运行一次的结果为:

e = error1
error2
error3
error4
true
true
true

即Join函数打包后的错误类型实例类型的Error方法会返回换行符间隔的错误列表。

2) 新增arena实验包

Go是带GC语言,虽然Go GC近几年持续改进,绝大多数场合都不是大问题了。但是在一些性能敏感的领域,GC过程占用的可观算力还是让应用吃不消。

降GC消耗,主要思路就是减少堆内存分配、减少反复的分配与释放。Go社区的某些项目为了减少内存GC压力,在mmaped内存上又建立一套GC无法感知到的简单内存管理机制并在适当场合应用。但这些自实现的、脱离GC的内存管理都有各自的问题。

Go 1.18版本发布前,arena这个proposal[17]就被提上了日程,arena包又是google内部的一个实验包,据说效果还不错的(在改进grpc的protobuf反序列化实验上),可以节省15%的cpu和内存消耗。但proposal一出,便收到了来自各方的comment,该proposal在Go 1.18和Go 1.19一度处于hold状态,直到Go 1.20才纳入到试验特性,我们可以通过GOEXPERIMENT=arena开启该机制。

arena包主要思路其实是“整体分配,零碎使用,再整体释放”,以最大程度减少对GC的压力。关于arena包,等进一步完善后,后续可能会有专门文章分析。

3) time包变化

time包增加了三个时间layout格式常量[18],相信不用解释,大家也知道如何使用:

DateTime   = "2006-01-02 15:04:05"DateOnly   = "2006-01-02"TimeOnly   = "15:04:05"

time包还为Time增加了Compare方法[19],适用于time之间的>=和<=比较:

// Compare returns -1 if t1 is before t2, 0 if t1 equals t2 or 1 if t1 is after t2.
func (t1 Time) Compare(t2 Time) int

此外,time包的RFC3339时间格式是使用最广泛的时间格式,其解析性能在Go 1.20中得到优化,提升了70%左右,格式化性能提升30%[20]

4. 其他

  • Go 1.17版本将作为Go 1.20的bootstrap编译器;

  • Go编译器性能提升3%[21]

  • Go工具链将根据GO[arch]环境变量的设置自动设置相关build tags[22]

  • 标准库增加crypto/ecdh包[23],提供安全的、基于byte切片的ECDH API;

  • bytes, strings包增加Clone函数[24]

  • strings包增加CutPrefix和CutSuffix函数[25]

  • text/template的解析性能提升40%[26]

5. 参考资料

  • Go 1.20 milestone - https://github.com/golang/go/milestone/250

  • Exploring Go's Profile-Guided Optimizations - https://www.polarsignals.com/blog/posts/2022/09/exploring-go-profile-guided-optimizations/

  • What's coming to go 1.20 - https://twitter.com/mvdan_/status/1588242469577117696


“Gopher部落”知识星球[27]旨在打造一个精品Go学习和进阶社群!高品质首发Go技术文章,“三天”首发阅读权,每年两期Go语言发展现状分析,每天提前1小时阅读到新鲜的Gopher日报,网课、技术专栏、图书内容前瞻,六小时内必答保证等满足你关于Go语言生态的所有需求!2022年,Gopher部落全面改版,将持续分享Go语言与Go应用领域的知识、技巧与实践,并增加诸多互动形式。欢迎大家加入!

outside_default.png

outside_default.png

outside_default.png

outside_default.png

Gopher Daily(Gopher每日新闻)归档仓库 - https://github.com/bigwhite/gopherdaily

我的联系方式:

  • 微博(暂不可用):https://weibo.com/bigwhite20xx

  • 微博2:https://weibo.com/u/6484441286

  • 博客:tonybai.com

  • github: https://github.com/bigwhite

outside_default.png

商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。

参考资料

[1] 

“Go, 13周年”: https://tonybai.com/2022/11/11/go-opensource-13-years

[2] 

Go泛型语法落地: https://tonybai.com/2022/04/20/some-changes-in-go-1-18

[3] 

Go1兼容性承诺: https://go.dev/doc/go1compat

[4] 

Go 1.20 milestone: https://github.com/golang/go/milestone/250

[5] 

1.18版本: https://tonybai.com/2022/04/20/some-changes-in-go-1-18

[6] 

“spec: allow conversion from slice to array”: https://github.com/golang/go/issues/46505

[7] 

Go 1.19版本: https://tonybai.com/2022/08/22/some-changes-in-go-1-19

[8] 

go playground: https://go.dev/play

[9] 

“profile-guided optimization”: https://github.com/golang/proposal/blob/master/design/55022-pgo.md

[10] 

内联: https://tonybai.com/2022/10/17/understand-go-inlining-optimisations-by-example

[11] 

“profile-guided optimization”: https://github.com/golang/go/issues/55022

[12] 

profile-guided optimization设计文档: https://github.com/golang/proposal/blob/master/design/55022-pgo-implementation.md

[13] 

Go团队决定从Go 1.20开始发行版不再为GOROOT中的大多数软件包提供预编译的.a文件: https://github.com/golang/go/issues/47257

[14] 

“extend code coverage testing to include applications”: https://github.com/golang/proposal/blob/master/design/51430-revamp-code-coverage.md

[15] 

这个是一个早计划好的“废弃动作”: https://github.com/golang/go/issues/41696

[16] 

增加了一种将多个error包装(wrap)为一个error的机制: https://github.com/golang/go/issues/53435#issuecomment-1191752789

[17] 

arena这个proposal: https://github.com/golang/go/issues/51317

[18] 

time包增加了三个时间layout格式常量: https://github.com/golang/go/issues/52746

[19] 

time包还为Time增加了Compare方法: https://github.com/golang/go/issues/50770

[20] 

其解析性能在Go 1.20中得到优化,提升了70%左右,格式化性能提升30%: https://github.com/golang/go/issues/50770

[21] 

Go编译器性能提升3%: https://go-review.googlesource.com/c/go/+/432897

[22] 

arch]环境变量的设置[自动设置相关build tags: https://github.com/golang/go/issues/45454

[23] 

增加cyypto/ecdh包: https://github.com/golang/go/issues/52221

[24] 

bytes, strings包增加Clone函数: https://github.com/golang/go/issues/45038

[25] 

strings包增加CutPrefix和CutSuffix函数: https://github.com/golang/go/issues/42537

[26] 

text/template的解析性能提升40%: https://github.com/golang/go/issues/53261

[27] 

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

这篇关于Go 1.20新特性前瞻的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/272369

相关文章

Go语言编译环境设置教程

《Go语言编译环境设置教程》Go语言支持高并发(goroutine)、自动垃圾回收,编译为跨平台二进制文件,云原生兼容且社区活跃,开发便捷,内置测试与vet工具辅助检测错误,依赖模块化管理,提升开发效... 目录Go语言优势下载 Go  配置编译环境配置 GOPROXYIDE 设置(VS Code)一些基本

Spring Boot3.0新特性全面解析与应用实战

《SpringBoot3.0新特性全面解析与应用实战》SpringBoot3.0作为Spring生态系统的一个重要里程碑,带来了众多令人兴奋的新特性和改进,本文将深入解析SpringBoot3.0的... 目录核心变化概览Java版本要求提升迁移至Jakarta EE重要新特性详解1. Native Ima

使用Go实现文件复制的完整流程

《使用Go实现文件复制的完整流程》本案例将实现一个实用的文件操作工具:将一个文件的内容完整复制到另一个文件中,这是文件处理中的常见任务,比如配置文件备份、日志迁移、用户上传文件转存等,文中通过代码示例... 目录案例说明涉及China编程知识点示例代码代码解析示例运行练习扩展小结案例说明我们将通过标准库 os

深入理解Go语言中二维切片的使用

《深入理解Go语言中二维切片的使用》本文深入讲解了Go语言中二维切片的概念与应用,用于表示矩阵、表格等二维数据结构,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起学习学习吧... 目录引言二维切片的基本概念定义创建二维切片二维切片的操作访问元素修改元素遍历二维切片二维切片的动态调整追加行动态

go中的时间处理过程

《go中的时间处理过程》:本文主要介绍go中的时间处理过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1 获取当前时间2 获取当前时间戳3 获取当前时间的字符串格式4 相互转化4.1 时间戳转时间字符串 (int64 > string)4.2 时间字符串转时间

Go语言中make和new的区别及说明

《Go语言中make和new的区别及说明》:本文主要介绍Go语言中make和new的区别及说明,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1 概述2 new 函数2.1 功能2.2 语法2.3 初始化案例3 make 函数3.1 功能3.2 语法3.3 初始化

从入门到精通C++11 <chrono> 库特性

《从入门到精通C++11<chrono>库特性》chrono库是C++11中一个非常强大和实用的库,它为时间处理提供了丰富的功能和类型安全的接口,通过本文的介绍,我们了解了chrono库的基本概念... 目录一、引言1.1 为什么需要<chrono>库1.2<chrono>库的基本概念二、时间段(Durat

Go语言中nil判断的注意事项(最新推荐)

《Go语言中nil判断的注意事项(最新推荐)》本文给大家介绍Go语言中nil判断的注意事项,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录1.接口变量的特殊行为2.nil的合法类型3.nil值的实用行为4.自定义类型与nil5.反射判断nil6.函数返回的

Go语言数据库编程GORM 的基本使用详解

《Go语言数据库编程GORM的基本使用详解》GORM是Go语言流行的ORM框架,封装database/sql,支持自动迁移、关联、事务等,提供CRUD、条件查询、钩子函数、日志等功能,简化数据库操作... 目录一、安装与初始化1. 安装 GORM 及数据库驱动2. 建立数据库连接二、定义模型结构体三、自动迁

Go语言代码格式化的技巧分享

《Go语言代码格式化的技巧分享》在Go语言的开发过程中,代码格式化是一个看似细微却至关重要的环节,良好的代码格式化不仅能提升代码的可读性,还能促进团队协作,减少因代码风格差异引发的问题,Go在代码格式... 目录一、Go 语言代码格式化的重要性二、Go 语言代码格式化工具:gofmt 与 go fmt(一)