大家好,我是煎鱼。
在公司的一直倒退中,一开始大多是大单体,革新慢了,一个仓库会有应用几十年的状况,仓库的规模根本是一直增大的过程。
影响之一就是会应用程序打包后的体积越来越大,不晓得被用哪里去了 … 明天要探讨的提案《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 modules),其有一个设计是基于语义化版本的标准。
如下图
版本格局为“主版本号. 次版本号. 订正号”,版本号的递增规定如下:
- 主版本号:当你做了不兼容的 API 批改。
- 次版本号:当你做了向下兼容的功能性新增。
- 订正号:当你做了向下兼容的问题修改。
Go modules 的原意是软件库都恪守这个标准,因而外部会有最小版本抉择的逻辑。
也就是一个模块往往依赖着许多其它许许多多的模块,并且不同的模块在依赖时很有可能会呈现依赖同一个模块的不同版本,Go 会把版本清单都整理出来,最终失去一个构建清单。
如下图:
你会发现最终构建进去的依赖版本很有可能是与预期的不统一,从而导致许多业务问题。最经典的就是 grpc-go、protoc-go、etcd 多版本兼容问题,让许多人苦楚不已。
Go 团队在这一块的设计是比拟理想化的,曹大也将其归类在 Go modules 的七宗罪之一了。而软件包的 init 函数乱初始化一堆的问题,也是有些似曾相识了。
总结
这个问题的解决方案(提案)依然在探讨中,显然 Go 团队更心愿软件库的作者可能束缚好本人的代码,不要乱初始化。
引入惰性初始化的形式如何,你怎么看?欢送在评论区留言和探讨。
文章继续更新,能够微信搜【脑子进煎鱼了】浏览,本文 GitHub github.com/eddycjy/blog 已收录,学习 Go 语言能够看 Go 学习地图和路线,欢送 Star 催更。
Go 图书系列
- Go 语言入门系列:初探 Go 我的项目实战
- Go 语言编程之旅:深刻用 Go 做我的项目
- Go 语言设计哲学:理解 Go 的为什么和设计思考
- Go 语言进阶之旅:进一步深刻 Go 源码
举荐浏览
- Go1.19 那些事:国产芯片、内存模型等新个性,你晓得多少?
- 太疯狂了,Go 程序说 nil 不是 nil…