大家好,我是炸鱼。公司在不断发展中,初期大多是大单位,转型缓慢。一个仓库会用上几十年,仓库的规模基本上是一个不断增加的过程。其中一个影响就是打包后的应用体积越来越大,不知道用在了哪里……今天要讨论的提案《proposal: language: lazy init imports to possibly import without side effects》与此有关。提案背景让我们观察一段非常简单的Go代码并研究它。如下代码:packagemainimport_"crypto/x509"funcmain(){}这个Go程序只有3行代码,看起来没什么。事实真的如此吗?可以执行如下命令查看初始化过程:$gobuild--ldflags=--dumpdepmain.go2>&1|grep输入输出:runtime.main->runtime..inputruntime.main->main..inputmain。.encrypt->crypto/x509..encrypto/x509..encrypt->bytes..encrypto/x509..encrypt->crypto/sha256..encrypto/x509..encrypt->编码/pem..encrypto/x509。.lock->errors..lockcrypto/x509..lock->sync..lockcrypto/x509..lock->crypto/aes..lockcrypto/x509..lock->crypto/cipher..lockcrypto/x509..lock->crypto/des..lock...context..lock->context.init.0vendor/golang.org/x/net/dns/dnsmessage..lock->vendor/golang.org/x/net/dns/dnsmessage.initvendor/golang.org/x/net/route..inittask->vendor/golang.org/x/net/route.initvendor/golang.org/x/net/route..inittask->vendor/golang.org/x/net/route.init.0...这个程序其实初始化了很多软件包(标准库,第三方包等)。这使得包大小为1.3MB到2.3MB。在一定规模上,这种影响被认为是非常昂贵的。如您所见,只有3行的Go程序没有做任何实质性的事情。对启动性能敏感的程序会比较难受,而普通程序久而久之也会进入恶性循环,启动会比平时慢。解决方案一起来看看解决方案和另外一个方案《proposal: spec: Go 2: allow manual control over imported package initialization》。核心思想是:引入惰性初始化(lazyinit),也就是业界常说的懒加载。也就是真正的import是在必要的时候才做的,并没有在importpackage的时候完成初始化。优化方向:主要是在导入包路径后添加惰性初始化声明,比如下面会提到的go:lazyinit或go:deferred注解。然后等程序真正使用起来,再正式初始化。1.go:lazyinit:packagemainimport("crypto/x509"//go:lazyinit"fmt")funcmain(){...}2.go:deferred:packagemainimport(_"github.com示例/eddycjy/core"//go:deferred_"github.com/eddycjy/util"//go:deferred)funcmain(){ifos.Args[1]!="util"{//现在使用这个包,开始初始化core,err:=runtime.InitDeferredImport("github.com/some/module/core")...}...}这样可以大大提高启动性能。讨论在大多数社区讨论中,这个提案实际上是又爱又恨。因为它看起来有一种合理的诉求,但是仔细想想就会发现,这是完全错误的。这个提案的背景和解决方案是治标不治本。因为根本原因是这样的:很多库误用了init函数,初始化了很多不必要的东西。Go核心开发团队认为应该由库作者来修复这些库,而不是让Go来“修复”问题。如果支持惰性初始化,也会给这些低质量库的作者提供继续这样做的借口。Déjàvu在写这篇文章时,我想起了Go的依赖管理(Gomodules),它有一个基于SemanticVersioning规范的设计。如下图版本格式为“主版本号.次版本号.修订号”。版本号的递增规则如下:主版本号:当你进行了不兼容的API更改时。次版本号:当你做向后兼容的功能添加时。修订号:当您进行向后兼容性错误修复时。Gomodules的初衷是所有的软件库都遵循这个规范,所以里面会有选择最低版本的逻辑。也就是说,一个模块往往依赖很多其他模块,当不同的模块依赖同一个模块的不同版本时,Go会整理版本列表,最后得到一个构建列表。如下图所示:你会发现依赖的最终版本很可能与预期的不一致,从而导致很多业务问题。最经典的就是grpc-go、protoc-go、etcd的多版本兼容问题,让很多人头疼。Go团队在这方面的设计比较理想,曹达也将其列为Go模块七大罪之一。而且软件包的init函数随机初始化了一堆问题,也有些似曾相识。综上所述,这个问题的解决方案(提案)还在讨论中。显然,Go团队希望软件库的作者能够约束他们的代码,不要随意初始化。引入惰性初始化怎么样,你怎么看?欢迎在评论区留言讨论。文章持续更新中。可以微信搜索【脑补炸鱼】阅读。本文已收录在GitHubgithub.com/eddycjy/blog中。学习Go语言可以看Go学习地图和路线。欢迎星星提醒。Go书系列Go语言入门系列:初探Go项目实战Go语言编程之旅:深入使用Go做项目Go语言设计哲学:理解Go的Why与设计思维Go语言进阶之旅:进一步深入-深度Go源码推荐阅读Go1。19那些事:国产芯片、内存型号等新特性你知道多少?Go程序说nil不是nil真是太疯狂了......
