golang 1.20更新


今天golang更新了一个里程碑golang 1.20版本,里面有一些优化和更改

核心变更

  1. 从Go 1.17新增的“转换一个切片为一个(指向其的)数组的指针”,Go 1.20 简化了这部分的语法,从原来的*(*[4]byte)(x) 简化到 [4]byte(x).

    这块语法的呼声挺高的,可能官方也是觉得之前的写法太绕了,不过从实际的工程出发,不建议做这样的转换,这里就不展开了.

  2. $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环境的情况下, 会更快更省带宽(缓存恢复也更快)

  3. 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环境来对应的编译.

  4. Cover 功能内置了, 使用 go build -cover就可以生成带测试覆盖率的二进制,然后配上 GOCOVERDIR=xxx目录就可以输出覆盖率报告到配置的目录.

    详细的说明参考这里: https://go.dev/testing/coverage/

    Cover同时支持 单元测试+集成测试, 本次1.20主要是增加了集成测试,这块还在研究中,不过看起来对于命令型的二进制比较方便,对于web服务型的,吸引力不是特别大(所有的接口的覆盖率测试,还是靠单元测试更靠谱一些),集成测试需要fake数据,对测试的要求更高了.

  5. 开始实验性质的支持 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%甚至以上的优化.

  6. Runtime 的优化, 部分GC内部数据结构重新组织了一下,更节约空间+CPU效率更高,这块最高能减少内存占用和CPU性能优化2%.

    基本每次大半崩更新都会有,放在这里意思意思,对用户使用来说是无感知的

  7. go1.18和go1.19的编译速度有一些劣化,主要是由对 generics 范型的相关工作引起的,在go1.20版本,编译速度提高了最多10%,回到了go1.17差不多的基线水平.

    范型的工作是比较长期的,1.20没有更多的功能更新,更多是在性能和gc等地方做了不少的优化.

  8. 还有些细节的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

comments powered by Disqus