原文在这里。
由 Robert Findley and Alan Donovan 发布于 2023年9月8日
今年夏天初,Go团队发布了gopls的v0.12版本,这是Go语言的语言服务器,它进行了核心重写,使其能够适应更大的代码库。这是一项长达一年的努力的成果,我们很高兴分享我们的进展,并稍微谈一下新的架构以及它对gopls未来的意义。
自v0.12版本发布以来,我们已经对新设计进行了微调,重点是使交互式查询(如自动完成或查找引用)的速度与v0.11相比保持不变,尽管内存中保存的状态要少得多。如果您还没有尝试过,我们希望您会尝试一下:
$ go install golang.org/x/tools/gopls@latest
我们很想通过这份简短的调查了解您对它的使用体验。
减少内存占用和启动耗时
在深入了解详细信息之前,让我们先来看一下结果!下面的图表显示了GitHub上最受欢迎的28个Go存储库的启动时间和内存使用情况的变化。这些测量是在打开一个随机选择的Go文件并等待gopls完全加载其状态后进行的,由于我们假设初始索引会在多个编辑会话中分摊,所以我们是在第二次打开文件时进行这些测量的。
五年前,gopls通过维护有状态的会话仅提供了性能的改进。而旧版命令行工具每次执行都必须从头开始,gopls可以保存中间结果以显著降低延迟。但所有这些状态都带来了一定的成本,随着时间的推移,我们越来越多地听到用户反馈,即gopls的高内存使用几乎难以忍受。
20世纪50年代,第一批编译器的设计者很快发现了单体编译的限制。他们的解决方案是将程序分为单元,并分别编译每个单元。独立编译使得可以将程序分成小块进行构建,即使程序无法全部放入内存也能构建完成。在Go中,单元是包(packages)。不同包的编译无法完全分开:当编译一个包P时,编译器仍然需要有关P导入的包提供了什么信息。为了安排这一点,Go构建系统在P本身之前编译了P导入的所有包,并且Go编译器编写了每个包的导出API的简洁摘要。P导入的包的摘要作为输入提供给P本身的编译。
Gopls v0.12将独立编译引入了gopls,重用了编译器使用的相同包摘要格式。这个想法很简单,但细节中有微妙之处。我们重写了以前检查表示整个程序的数据结构的每个算法,使其现在一次只处理一个包,并将每个包的结果保存到文件中,就像编译器发出对象代码一样。例如,查找对函数的所有引用曾经是在程序数据结构中搜索特定指针值的所有出现的情况一样容易。现在,当gopls处理每个包时,它必须构建并保存一个索引,将源代码中每个标识符的位置与它所引用的符号的名称关联起来。在查询时,gopls加载和搜索这些索引。其他全局查询,如“查找实现”,使用类似的技术。