当前位置:网站首页>Go 程序太大了,能要个延迟初始化不?
Go 程序太大了,能要个延迟初始化不?
2022-08-02 03:28:00 【hebiwen95】
提案
背景
我们来观察一段很简单的 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[2]》一起来看。
核心思想是:引入惰性初始化(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 团队更希望软件库的作者能够约束好自己的代码,不要乱初始化。
引入惰性初始化的方式如何,你怎么看?欢迎在评论区留言和讨论。
边栏推荐
猜你喜欢
对账、结账、错账更正方法、划线更正法、红字更正法、补充登记法
财产清查概述、 全面清查的情况、局部清查的情况、财产清查的方法、财产清查结果的处理
链动2+1模式开发系统
kotlin语法总结(二)
最简单的FRP内网穿透教程
The shooting range that web penetration must play - DVWA shooting range 1 (centos8.2+phpstudy installation environment)
CTF-Neting Cup Past Topics
whistle 手机调试代理工具
Kotlin - 标准函数(with、run和apply)
清理c盘爆满告急,C盘清理
随机推荐
Anaconda报错:An unexpected error has occurred. Conda has prepared the above report 解决办法
Google Hacking
Laravel打印执行的SQL语句
CSRF (Cross Site Request Forgery)
gradle脚本中groovy语法讲解
库存现金、现金管理制度、现金的账务处理、银行存款、银行存款的账务处理、银行存款的核对
树莓派4b安装win11/10过程全教程(附蓝屏inaccessible boot device解决办法)
Binder机制详解(二)
Win10 解决AMD平台下SVM无法开启的问题
【一句话攻略】彻底理解JS中的回调(Callback)函数
张量乘积—实验作业
ReentrantLock的使用和原理详解
redis未授权访问(4-unacc)
链动2+1模式开发系统
账务处理程序、记账凭证账务处理程序、汇总记账凭证账务处理程序、科目汇总表账务处理程序、会计信息化概述、信息化环境下会计账务处理的基本要求(此章出1道小题)
win10内存占用很高,关闭所有应用程序依然降不下来(win11)
Summary of php function vulnerabilities
Laravel 登录,中间件和路由分组
ontop-vkg 学习1
cmd控制台窗体大小设置