当前位置:网站首页>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 团队更希望软件库的作者能够约束好自己的代码,不要乱初始化。
引入惰性初始化的方式如何,你怎么看?欢迎在评论区留言和讨论。
边栏推荐
猜你喜欢
Windows下MySQL数据库报“ERROR 2003 (HY000): Can‘t connect to MySQL server on ‘localhost:8000‘ (10061)”错误解决
3000字入门图神经网络
面试必备:Android性能分析与优化实战进阶手册
考(重点理解哪些属于其他货币资金)、其他货币资金的内容、其他货币资金的账务处理(银行汇票存款、银行本票存款、信用卡存款、信用证保证金存款、存出投资款、外埠存款)
会计凭证概述、原始凭证、原始凭证的种类、原始凭证的基本内容、原始凭证的填制要求、原始凭证的审核
公司产品太多了,怎么实现一次登录产品互通?
WeChat applet development video loading: [Rendering layer network layer error] Failed to load media
树莓派4b安装win11/10过程全教程(附蓝屏inaccessible boot device解决办法)
zsh: command not found: xxx 解决方法
CSRF (Cross Site Request Forgery)
随机推荐
laravel-admin 线上访问项目,一直重定向到登录页面
关于我的项目-实现一个数据库~
Solve the problem that the 5+APP real machine test cannot access the background (same local area network)
重点考:从债劵的角度来看交易性金融资产
Laravel打印执行的SQL语句
会计凭证概述、原始凭证、原始凭证的种类、原始凭证的基本内容、原始凭证的填制要求、原始凭证的审核
英语每日打卡
OpenCore 黑苹果安装教程
浅谈性能优化:APP的启动流程分析与优化
ReentrantLock的使用和原理详解
还原最真实、最全面的一线大厂面试题
在 UUP dump 被墙的情况下如何用 UUP 下载 ISO 镜像
英语每日打卡
备战金九银十:Android 高级架构师的学习路线及面试题分享
同时安装VirtualBox和VMware,虚拟机如何上网
2021-09-04 最简单的Golang定时器应用以及最简单的协程入门儿
元宇宙:为何互联网大佬纷纷涉足?元宇宙跟NFT是什么关系?
深入了解为何面试官常说:你还没准备好,我不会录用你
2022年中高级 Android 大厂面试秘籍,为你保驾护航金九银十,直通大厂
C# 常用方法记录