大家好,我是煎鱼

在公司的不断发展中,一开端大多是大单体,改造慢了,一个仓库会有运用几十年的情况,仓库的规模基本是不断增大的进程。

影响之一便是会应用程序打包后的体积越来越大,不知道被用哪里去了…今天要谈论的提案《proposal: language: lazy init imports to possibly import without side effects》,就与此有关。

提案

布景

咱们来调查一段很简单的 Go 代码,研讨研讨。如下代码:

package main
import _ "crypto/x509"
func main() {}

这个 Go 程序只要 3 行代码,看起来就没有任何东西。实际上是这样吗?

咱们可以执行以下命令看看初始化进程:

$ go build --ldflags=--dumpdep main.go 2>&1 | grep inittask

输出结果:

runtime.main -> runtime..inittask
runtime.main -> main..inittask
main..inittask -> crypto/x509..inittask
crypto/x509..inittask -> bytes..inittask
crypto/x509..inittask -> crypto/sha256..inittask
crypto/x509..inittask -> encoding/pem..inittask
crypto/x509..inittask -> errors..inittask
crypto/x509..inittask -> sync..inittask
crypto/x509..inittask -> crypto/aes..inittask
crypto/x509..inittask -> crypto/cipher..inittask
crypto/x509..inittask -> crypto/des..inittask
...
context..inittask -> context.init.0
vendor/golang.org/x/net/dns/dnsmessage..inittask -> vendor/golang.org/x/net/dns/dnsmessage.init
vendor/golang.org/x/net/route..inittask -> vendor/golang.org/x/net/route.init
vendor/golang.org/x/net/route..inittask -> vendor/golang.org/x/net/route.init.0
...

这段程序其实初始化了超级多的软件包(标准库、第三方包等)。使得包的的巨细从标准的 1.3 MB 变成了 2.3 MB。

在必定规模下,大家以为该影响是非常贵重的。因为你可以看到只要 3 行的 Go 程序并没有做任何实质性的工作。

对发动功能敏感的程序会比较难受,一般程序也会随着铢积寸累进入恶性循环,发动会比惯例的更慢。

计划

在处理计划上咱们结合另外一个提案《proposal: spec: Go 2: allow manual control over imported package initialization》一同来看。

中心思维是:引进惰性初始化(lazy init),业内也常称为推迟加载。也便是必要的时候再真实的导入,不在引进包时就完成初始化。

优化方向上:主要是在导入包途径后增加懒惰初始化的声明,例如在下方即将会说到的:go:lazyinit 或 go:deferred 注解。再等待程序真实运用届时再正式初始化。

1、go:lazyinit 的比如:

package main
import (
      "crypto/x509" // go:lazyinit
      "fmt"
)
func main() {...}

2、go:deferred 的比如:

package main
import (
    _ "github.com/eddycjy/core" // go:deferred
    _ "github.com/eddycjy/util" // go:deferred
)
func main() {
    if os.Args[1] != "util" {
        // 现在要运用这个包,开端初始化
        core, err := runtime.InitDeferredImport("github.com/some/module/core")
        ...
    }
    ...
}

以此来实现,可以大大提高发动功能。

谈论

实际上在大多数的社区谈论中,对这个提案是又爱又恨。因为它好像又有合理的诉求,但细思好像又会发现完全不对劲。

这个提案的布景和处理计划,是治标不治本的。因为底子原因是:许多库滥用了 init 函数,让许多不必要的东西都初始化了。

Go 程序太大了,能要个延迟初始化不?

Go 中心开发团队以为让库作者去修复这些库,而不是让 Go 来 “处理” 这些问题。假如支持惰性初始化,也会为这些低质量库的作者供给继续这样做的托言。

似曾相识的感觉

在写这篇文章时,我想起了 Go 的依靠办理(Go modules),其有一个设计是根据语义化版别的标准。

如下图

Go 程序太大了,能要个延迟初始化不?

版别格局为 “主版别号.次版别号.修订号”,版别号的递加规矩如下:

  • 主版别号:当你做了不兼容的 API 修正。
  • 次版别号:当你做了向下兼容的功能性新增。
  • 修订号:当你做了向下兼容的问题修正。

Go modules 的原意是软件库都遵守这个标准,因此内部会有最小版别选择的逻辑。

也便是一个模块往往依靠着许多其它许许多多的模块,并且不同的模块在依靠时很有可能会出现依靠同一个模块的不同版别,Go 会把版别清单都整理出来,最终得到一个构建清单。

如下图:

Go 程序太大了,能要个延迟初始化不?

你会发现最终构建出来的依靠版别很有可能是与预期的不一致,从而导致许多业务问题。最经典的便是 grpc-go、protoc-go、etcd 多版别兼容问题,让许多人痛苦不已。

Go 团队在这一块的设计是比较理想化的,曹大也将其归类在 Go modules 的七宗罪之一了。而软件包的 init 函数乱初始化一堆的问题,也是有些似曾相识了。

总结

这个问题的处理计划(提案)仍然在谈论中,明显 Go 团队更期望软件库的作者可以约束好自己的代码,不要乱初始化。

引进惰性初始化的方法怎么,你怎么看?欢迎在谈论区留言和谈论。

文章继续更新,可以微信搜【脑子进煎鱼了】阅览,本文 GitHub github.com/eddycjy/blo… 已收录,学习 Go 言语可以看 Go 学习地图和路线,欢迎 Star 催更。

Go 图书系列

  • Go 言语入门系列:初探 Go 项目实战
  • Go 言语编程之旅:深化用 Go 做项目
  • Go 言语设计哲学:了解 Go 的为什么和设计思考
  • Go 言语进阶之旅:进一步深化 Go 源码

推荐阅览

  • Go1.19 那些事:国产芯片、内存模型等新特性,你知道多少?
  • 太疯狂了,Go 程序说 nil 不是 nil…