golang 1.20更新
今天golang更新了一个里程碑golang 1.20版本,里面有一些优化和更改
核心变更
从Go 1.17新增的“转换一个切片为一个(指向其的)数组的指针”,Go 1.20 简化了这部分的语法,从原来的
*(*[4]byte)(x)
简化到[4]byte(x)
.这块语法的呼声挺高的,可能官方也是觉得之前的写法太绕了,不过从实际的工程出发,不建议做这样的转换,这里就不展开了.
$GOROOT/pkg
目录下面不再默认打包预编译好的二进制产物,而是在第一次需要的时候才打包和缓存起来,这样可以极大的减少go安装包的大小,同时可以避免一些cgo的编译问题(下面会提到)这个改动底层的影响比较大,主要是
CGO
的部分,对于下载的安装包,确实减少了很多:ls -lt go1.19.5.linux-amd64.tar.gz -rw-r--r--@ 1 zzjin staff 148949578 Jan 11 10:57 go1.19.5.linux-amd64.tar.gz ls -lt go1.20.linux-amd64.tar.gz -rw-r--r--@ 1 zzjin staff 99869470 Feb 2 12:25 go1.20.linux-amd64.tar.gz
从
142M
减小到了95M
, 减少了 32.95% , 对于开发来说可能意义不大, 不过对于CI等各种需要多次初始化go环境的情况下, 会更快更省带宽(缓存恢复也更快)Go1.20 版本, 如果系统没有
C
工具链的环境,CGO
会默认设置为禁用.准确来说, 如果
CGO_ENABLED
环境变量没有被设置, 且CC
环境变量没有被设置, 且环境路径下面没有找到默认的C
编译器(主要是clang
/gcc
)的话,CGO_ENABLED
会被默认设置为0. 同样,可以手动在环境量设置CGO_ENABLED
来覆盖这个默认行为.这块主要的目的是在一些没有安装c编译器的环境下更好的使用go,比如在一些轻量级的 docker 镜像中,或者 mac 下,这时候, go在编译package的时候会先尝试使用纯go实现的包,然后再尝试编译cgo的包(直到出错).
标准库里面用到cgo实现的包有
net
,os/user
,plugin
. MacOS环境下的net
,os/user
已经又用纯go源码实现了一遍; Windows环境下的net
,os/user
从来没有使用过cgo代码来实现; 别的环境下面,会根据是否有cgo环境来对应的编译.Cover
功能内置了, 使用go build -cover
就可以生成带测试覆盖率的二进制,然后配上GOCOVERDIR=xxx目录
就可以输出覆盖率报告到配置的目录.详细的说明参考这里: https://go.dev/testing/coverage/
Cover同时支持
单元测试
+集成测试
, 本次1.20主要是增加了集成测试,这块还在研究中,不过看起来对于命令型的二进制比较方便,对于web服务型的,吸引力不是特别大(所有的接口的覆盖率测试,还是靠单元测试更靠谱一些),集成测试需要fake数据,对测试的要求更高了.开始实验性质的支持
PGO
/Profile Guided Optimization
. 目前的实现编译器支持pprof CPU
, 就用之前的runtime/pprof
,net/http/pprof
包生成的数据就可以了.在新编译二进制的时候加上
go build -pgo=/path/to/pprof
就可以了.目前go1.20使用pprof数据来优化热访问的函数的inline编译方式,测试显示已经能有3-4%的性能优化.随着迭代肯定会加入更多的优化方式与实现.
stateDiagram-v2 state "编译并执行一个初始的二进制(没有启用pgo)" as s1 state "在生产环境中收集pprof数据" as s2 state "在合适的时候,\n(需要更新版本或压力运行一段时间后)\n使用pprof数据编译新的二进制" as s3 s1 --> s2 s2 --> s3: 持续运行 s3 --> s2: 反馈数据详细的说明参考这里: https://go.dev/doc/pgo
PGO大法好,这块目前还比较简陋,只支持基于cpu的优化,但是空间非常大,可以很好的延续go的web与底层环境下面的优势,特别对于
utils
/pkg
这类的库来说(项目里面的helper
包也是), 在web应用的ppprof,和在底层作为工具/后台的进程应用来说,差异会比较大,用了pgo之后应该能有直接的5%甚至以上的优化.Runtime
的优化, 部分GC内部数据结构重新组织了一下,更节约空间+CPU效率更高,这块最高能减少内存占用和CPU性能优化2%.基本每次大半崩更新都会有,放在这里意思意思,对用户使用来说是无感知的
go1.18和go1.19的编译速度有一些劣化,主要是由对
generics
范型的相关工作引起的,在go1.20版本,编译速度提高了最多10%,回到了go1.17差不多的基线水平.范型的工作是比较长期的,1.20没有更多的功能更新,更多是在性能和gc等地方做了不少的优化.
还有些细节的
arch
,vet
,linker
相关的很多优化, 都可以跳转原文详细了解.
完整的更新说明请详细的阅读原文: https://go.dev/doc/go1.20
内置包的更新
每次内置包都有更新,这里只摘说可能会影响到的几个包
errors
,fmt
errors
新增了一个errors.Join()
,现在可以wrap多个error错误了,同时可以判断这个wrap是否包含了其中的任何一个错误.package main import ( "errors" "fmt" ) func main() { err1 := errors.New("err1") err2 := errors.New("err2") err := errors.Join(err1, err2) fmt.Println(err) if errors.Is(err, err1) { // true fmt.Println("err is err1") } if errors.Is(err, err2) { // also true fmt.Println("err is err2") } }
archive/tar
,archive/zip
如果设置了环境变量
GODEBUG=tarinsecurepath=0
, 如果读到了一个绝对路径,或者指向一个当前目录外的路径,或者包含了非法字符,或者包含保留字符(比如在windows上的NUL),Reader.Next
方法都会返回一个ErrInsecurePath
类型的错误.crypto/ecdsa
,crypto/ed25519
,crypto/rsa
,crypto/subtle
,crypto/tls
,crypto/x509
加密的算法更安全了,但是也更慢了, 还有一些解析的错误处理的变更.
如果用到了相关的库,最好仔细阅读一下更新说明: https://go.dev/doc/go1.20#context
math/rand
math/rand
包现在自动应用全局的rand随机数种子,来保证随机数函数返回一定是有效的(比如直接调用rand.Int()).如果代码里面需要确定的可复现的随机数队列,需要自己初始化一个本地的对象.(这个在测试用例面比较常见). 同时全局的
rand.Seed
函数配标记为废弃,之前有引用的地方需要修改一下.func Rand(a int) int { - rand.Seed(time.Now().UnixNano()) - return rand.Intn(a) + rnd := rand.New(rand.NewSource(time.Now().UnixNano())) + return rnd.Intn(a) }
net
LookupCNAME
函数会返回cname的值,而不管实际这个值是否有其对应的任意A
,AAAA
,CNAME
记录.换句话说,只要cname本身记录存在,就会返回成功
DNS resolution
会持续的监听/etc/nsswitch.conf
文件的变更, 最多5s一次,和监听/etc/hosts
,/etc/resolv.conf
保持一致.
net/http
ResponseWriter.WriteHeader
函数现在可以返回 1xx 状态码了.cookie 的名字的前后的空白字符会过滤掉,然后设置值,而不是像之前那样报错.
比如 “name =value"表示设置一个名字为"name"的cookie的值,而不是被认为是非法字符.
runtime/metrics
增加一些新的metrics:
- 当前的 GOMAXPROCS 设置 (/sched/gomaxprocs:threads)
- cgo命令的执行次数 (/cgo/go-to-c-calls:calls)
- 全局锁的总锁定时间 (/sync/mutex/wait/total)
- GC当中的一些行为的时间的记录
time
导出了常量:
DateTime
(“2006-01-02 15:04:05”)DateOnly
(“2006-01-02”)TimeOnly
(“15:04:05”)
方便直接使用,不用每个包都自己再写一遍了, 聊胜于无.
新增了一个
Time.Compare
方法.
上面的列表并不全面,主要是我们实际项目中会遇到的点做了介绍.
完整的更新说明请详细的阅读原文: https://go.dev/doc/go1.20