静态资源嵌入的提案被接受后争论最多的就是是否应该对资源采取压缩,压缩后的资源更紧凑,不会浪费太多存储空间,特别是一些大文本文件。同时更大的程序运行加载时间越长,cpu缓存利用率可能会变低。
而反对意见认为压缩和运行时的解压一个浪费编译的时间一个浪费运行时的效率,在用户没有明确指定的情况下用户需要为自己不需要的功能花费代价。
目前官方采用的实现是不压缩嵌入资源,并预计在后续版本加入控制是否启用压缩的选项。
而真正的陷阱是接下来的内容。
潜在的嵌入资源副本前文中提到过重复的匹配和相同的文件golang会自动只保留一份在变量中。没错,然而这是针对同一个变量的多个匹配说的,如果考虑下面的代码:
package main import ( _ "embed" "fmt" ) //go:embed imgs/screenrecord.gif var b []byte //go:embed imgs/screenrecord.gif var a []byte func main() { fmt.Printf("a: %p %d\n", &a, len(a)) fmt.Printf("b: %p %d\n", &b, len(b)) }猜猜输出是什么:
a: 0x9ff5a50 81100466 b: 0x9ff5a70 81100466a和b的地址不一样!那也没关系,我们知道slice是引用类型,底层说不定引用了同一个数组呢?那再来看看文件大小:
tree -sh . . ├── [ 484] embed_fs.go ├── [ 230] embed_img2.go ├── [157M] embed_img2 ├── ... ├── [ 0] imgs │ ├ ... │ └── [ 77M] screenrecord.gif ├── ... 4 directories, 19 files程序是资源的两倍大,这差不多就可以说明问题了,资源被复制了一份。不过从代码的角度来考虑,a和b是两个不同的对象,所以引用不同的数据也说的过去,但在开发的时候一定要小心,不要让两个资源集合出现交集,否则就要付出高昂的存储空间代价了。
过大的可执行文件带来的性能影响程序文件过大会导致初次运行加载时间的增长,这是众所周知的。
然而过大的程序文件还可能会降低运行效率。程序需要利用现代的cpu快速缓存体系来提高性能,而更大的二进制文件意味着对于反复运行的热点功能cpu的快速缓存很可能会面临更多的缓存失效,因为缓存的大小有限,需要两次三次的读取和刷新才能运行完一个热点代码片段。这就是为什么几乎所有的编译器都会自行指定函数是否会被内联化而不是把这种控制权利移交给用户的原因。
然而嵌入静态文件之后究竟会对性能有多少影响呢?目前缺乏实验证据,所以没有定论。
通过修改二进制文件的一部分格式也可以让代码部分和资源部分分离从而代码在cpu看来更加紧凑,当然这么做会不会严重破坏兼容,是否真的有用也未可知。
会被忽略的目录前面说过,embed会递归处理目录,出来以下的几个:
.git
.svn
.bzr
.hg
这些都是版本控制工具的目录,资源里理应不包含他们,因此是被忽略的。会被忽略的目录列在src/cmd/go/internal/load/pkg.go的isBadEmbedName函数里。
.idea不在此列,小心:P
总结使用golang1.16你可以更轻松地创建嵌入资源,不过在享受便利的同时也要注意利弊取舍,使用docker管理资源和部署也不失为一种好方法。
想要进一步测试也可以在这里下载本文的代码:https://github.com/apocelipes/embed-example
参考https://go.googlesource.com/proposal/+/master/design/draft-embed.md
https://github.com/golang/go/commit/25d28ec55aded46e0be9c2298f24287d296a9e47
大神的embed demo: https://github.com/mattn/go-embed-example