vgo cache
Problem
vgo 目前还在开发中,相比2个月前,vgo已经从一个原型开始慢慢成熟了,但是仍然还有许多问题需要解决。
这个问题是社区用户反馈的,vgo在fetch依赖的时候并不是并发执行的,所以很耗时间。rsc 于是在vgo 中添加了 par
包,它可以按指定数量并发执行某一项特定工作。同时为了减少网络和硬盘开销,rsc 为 vgo 添加了 cache 功能。
cache
不得不说 cache 的增加大大增加了 vgo model fetch 模块的复杂度,同时引入新的bug也是在所难免的,但是cache所带来的收益也是显而易见的,就像现在 go build 和 go test 中大家使用的cache一样,你感受不到它的存在,但是没有它,你会很难受。
type Repo
是外部操作一个远程代码库的接口,具体方法如下:
type Repo interface {
// ModulePath returns the module path.
ModulePath() string
// Versions lists all known versions with the given prefix.
// Pseudo-versions are not included.
// Versions should be returned sorted in semver order
// (implementations can use SortVersions).
Versions(prefix string) (tags []string, err error)
// Stat returns information about the revision rev.
// A revision can be any identifier known to the underlying service:
// commit hash, branch, tag, and so on.
Stat(rev string) (*RevInfo, error)
// Latest returns the latest revision on the default branch,
// whatever that means in the underlying source code repository.
// It is only used when there are no tagged versions.
Latest() (*RevInfo, error)
// GoMod returns the go.mod file for the given version.
GoMod(version string) (data []byte, err error)
// Zip downloads a zip file for the given version
// to a new file in a given temporary directory.
// It returns the name of the new file.
// The caller should remove the file when finished with it.
Zip(version, tmpdir string) (tmpfile string, err error)
}
当然 cache 也要实现这些接口,以方便外部无缝接入。
type cachingRepo struct {
path string
cache par.Cache // cache for all operations
r Repo
}
cache 结构体为每一个repo准备了一个 par.cache
用于cache相应的数据。当然还要有codeHost返回的 Repo,用于没有命中cache的情况下进行相应的操作。我们看看 par.cache
的实现。
// Cache runs an action once per key and caches the result.
type Cache struct {
m sync.Map
}
// Do calls the function f if and only if Do is being called for the first time with this key.
// No call to Do with a given key returns until the one call to f returns.
// Do returns the value returned by the one call to f.
func (c *Cache) Do(key interface{}, f func() interface{}) interface{} {
type entry struct {
once sync.Once
result interface{}
}
entryIface, ok := c.m.Load(key)
if !ok {
entryIface, _ = c.m.LoadOrStore(key, new(entry))
}
e := entryIface.(*entry)
e.once.Do(func() {
e.result = f()
})
return e.result
}
非常精简的设计,令人发指。为了避免并发带来的问题,直接使用 sync.Map
存储 entry。entry存储了两组信息:1.这个key的操作是否执行过,用 sync.Once来实现 2. 如果执行过了,结果存储在result中。简单举个例子,在一次 vgo build 中你可能会下载很多依赖,如果有个依赖被不同的依赖包引用过多次,那么它只会被下载一次,后面直接复用,另外,vgo会查找这个代码库的tag列表,用于版本管理,这个fetch tag列表的操作也会只进行一次。
在代码中合适的使用sync.Onec,会大大精简你的代码,提高你程序的性能。
References: