原文在这里

由 Robert Findley and Alan Donovan 发布于 2023年9月8日

今年夏天初,Go团队发布了goplsv0.12版本,这是Go语言的语言服务器,它进行了核心重写,使其能够适应更大的代码库。这是一项长达一年的努力的成果,我们很高兴分享我们的进展,并稍微谈一下新的架构以及它对gopls未来的意义。

自v0.12版本发布以来,我们已经对新设计进行了微调,重点是使交互式查询(如自动完成或查找引用)的速度与v0.11相比保持不变,尽管内存中保存的状态要少得多。如果您还没有尝试过,我们希望您会尝试一下:

$ go install golang.org/x/tools/gopls@latest

我们很想通过这份简短的调查了解您对它的使用体验。

减少内存占用和启动耗时

在深入了解详细信息之前,让我们先来看一下结果!下面的图表显示了GitHub上最受欢迎的28个Go存储库的启动时间和内存使用情况的变化。这些测量是在打开一个随机选择的Go文件并等待gopls完全加载其状态后进行的,由于我们假设初始索引会在多个编辑会话中分摊,所以我们是在第二次打开文件时进行这些测量的。

在这些存储库中,节省的平均值约为75%,但内存减少是非线性的:随着项目变得越来越大,内存使用的相对减少也会增加。我们将在下面更详细地解释这一点。

Gopls和不断发展的Go生态系统

Gopls提供了类似IDE的功能,如自动完成、格式化、交叉引用和重构等,适用于与语言无关的编辑器。自2018年开始,gopls已经整合了许多不同的命令行工具,如gurugorenamegoimports,成为了VS Code Go扩展以及许多其他编辑器和LSP插件的默认后端。也许你一直在使用gopls,而甚至不知道它的存在,这正是我们的目标!

五年前,gopls通过维护有状态的会话仅提供了性能的改进。而旧版命令行工具每次执行都必须从头开始,gopls可以保存中间结果以显著降低延迟。但所有这些状态都带来了一定的成本,随着时间的推移,我们越来越多地听到用户反馈,即gopls的高内存使用几乎难以忍受。

与此同时,Go生态系统不断增长,越来越多的代码被写入了更大的存储库。Go工作区允许开发人员同时处理多个模块,并且容器化开发将语言服务器放入了资源受限的环境中。代码库变得越来越大,开发环境变得越来越小。我们需要改变gopls的扩展方式,以跟上这一发展趋势。

重新审视gopls的编译器起源

在许多方面,gopls类似于一个编译器:它必须读取、解析、类型检查和分析Go源文件,为此它使用了Go标准库golang.org/x/tools模块提供的许多编译器构建块。这些构建块使用了“符号编程”的技术:在运行编译器时,每个函数(如fmt.Println)都有一个单一的对象或“符号”代表。对于函数的任何引用都表示为指向其符号的指针。要测试两个引用是否指的是同一个符号,您不需要考虑名称。您只需比较指针。指针比字符串要小得多,指针比较非常便宜,因此符号是表示一个像程序这样复杂的结构的高效方式。

为了快速响应请求,gopls v0.11将所有这些符号都保存在内存中,就好像gopls一次性编译了整个程序。结果是内存占用量与正在编辑的源代码成比例,并且远远大于源文本(例如,类型化语法树通常比源文本大30倍!)。

独立编译

20世纪50年代,第一批编译器的设计者很快发现了单体编译的限制。他们的解决方案是将程序分为单元,并分别编译每个单元。独立编译使得可以将程序分成小块进行构建,即使程序无法全部放入内存也能构建完成。在Go中,单元是包(packages)。不同包的编译无法完全分开:当编译一个包P时,编译器仍然需要有关P导入的包提供了什么信息。为了安排这一点,Go构建系统在P本身之前编译了P导入的所有包,并且Go编译器编写了每个包的导出API的简洁摘要。P导入的包的摘要作为输入提供给P本身的编译。

Gopls v0.12将独立编译引入了gopls,重用了编译器使用的相同包摘要格式。这个想法很简单,但细节中有微妙之处。我们重写了以前检查表示整个程序的数据结构的每个算法,使其现在一次只处理一个包,并将每个包的结果保存到文件中,就像编译器发出对象代码一样。例如,查找对函数的所有引用曾经是在程序数据结构中搜索特定指针值的所有出现的情况一样容易。现在,当gopls处理每个包时,它必须构建并保存一个索引,将源代码中每个标识符的位置与它所引用的符号的名称关联起来。在查询时,gopls加载和搜索这些索引。其他全局查询,如“查找实现”,使用类似的技术。

与go build命令一样,gopls现在使用基于文件的缓存存储来记录从每个包计算的信息摘要,包括每个声明的类型、交叉引用的索引和每个类型的方法集。由于缓存在进程之间保持不变,您会注意到第二次在工作区启动gopls时,它变得更快地准备好提供服务,如果运行两个gopls实例,它们可以协同工作。

这个改变的结果是,gopls的内存使用量与打开的包数量及其直接导入相关。这就是为什么在上面的图表中我们观察到了次线性的扩展:随着存储库变得更大,任何一个打开的包所观察到的项目的比例变得更小。

失效的细粒度

当您在一个包中进行更改时,只需要重新编译导入该包的包,不论是直接还是间接导入。这个想法是自20世纪70年代的Make以来所有增量构建系统的基础,自gopls创立以来一直在使用。实际上,在支持LSP的编辑器中的每次按键都会启动一个增量构建!然而,在大型项目中,间接依赖关系会累积,使这些增量重建变得过于缓慢。事实证明,很多这些工作并不是绝对必要的,因为大多数更改,例如在现有函数中添加语句,不会影响导入摘要。

如果您在一个文件中进行小的更改,我们必须重新编译它的包,但如果更改不影响导入摘要,我们不必编译任何其他包。更改的效果被“剪枝”了。一个影响到导入摘要的更改需要重新编译直接导入该包的包,但大多数这种更改不会影响这些包的导入摘要,如果是这样,效果仍然被剪枝,避免了重新编译间接导入者。由于这种剪枝,很少有一个低级包中的更改需要重新编译所有间接依赖于该包的包。剪枝的增量重建使得工作量与每个更改的范围成正比。这不是一个新的想法:它由Vesta引入,并且也在go build中使用。

v0.12版本引入了类似的剪枝技术到gopls,更进一步实现了基于语法分析的更快的剪枝启发式。通过保持内存中的符号引用简化图,gopls可以快速确定包c中的更改是否可能通过一系列引用影响包a。

在上面的示例中,从a到c没有引用链,因此即使a间接依赖于c,a也不会受到c中更改的影响。

新的可能性

虽然我们对我们取得的性能改进感到满意,但我们也对几个gopls功能感到兴奋,因为现在gopls不再受内存限制。

第一个是强大的静态分析。以前,我们的静态分析驱动程序必须在gopls的内存表示的包上运行,因此无法分析依赖关系:这样做会引入太多的额外代码。去掉这个要求后,我们能够在gopls v0.12中包含一个新的分析驱动程序,该驱动程序分析所有依赖关系,从而提高了精度。例如,gopls现在会报告Printf格式错误,即使是您在fmt.Printf周围的用户定义包装器也是如此。值得注意的是,多年来,go vet一直提供了这种精度,但是gopls在每次编辑后实时进行此操作是不可能的。现在可以了。

第二个是更简单的工作区配置更好的构建标签处理。这两个功能都意味着当您在计算机上打开任何Go文件时,gopls都会“做正确的事情”,但是在没有优化工作的情况下都是不可行的,因为(例如)每个构建配置都会增加内存占用!

赶快尝试吧

除了可扩展性和性能改进之外,我们还修复了许多已报告的错误,以及在转换期间提高测试覆盖率时发现的许多未报告的错误。

要安装最新的gopls:

$ go install golang.org/x/tools/gopls@latest

请尝试一下并填写调查问卷 - 如果遇到错误,请报告它,我们将进行修复。


声明:本作品采用署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)进行许可,使用时请注明出处。

Author: mengbin

blog: mengbin

Github: mengbin92

cnblogs: 恋水无意


为不断增长的Go生态系统扩展gopls的更多相关文章

  1. kubernetes1.4 基础篇:Learn Kubernetes 1.4 by 6 steps

    本教程受Kubernetes官方最新更新的文档所触发,之所以没有做单纯的翻译是因为如下几个原因: Kubernetes官方此教程基于minikube,个人对minikube可能有偏见,觉得像玩具. M ...

  2. Angular2 VS Angular4 深度对比:特性、性能

    欢迎大家持续关注葡萄城控件技术团队博客,更多更好的原创文章尽在这里~~​ 在Web应用开发领域,Angular被认为是最好的开源JavaScript框架之一. Google的Angular团队已于3月 ...

  3. 边缘计算、区块链、5G,哪个能走的更远

    频繁出现的新词汇5G.区块链.边缘计算,这些都代表了什么,又能给我们的生活带来什么巨大的改变么?抉择之时已至,能够走向未来的真的只有一个吗? "没有什么能够阻挡,你对自由的向往....&qu ...

  4. 半导体巨头青睐物联网领域 众强联手打造MCU生态系统

    随着万物互联的时代到来,众多半导体巨头纷纷转战物联网领域.早在十年前,意法半导体曾将STM32推向市场,意法半导体对32位MCU在物联网方面的应用在两年前就已展开攻势. 4月25日,历经两届盛况的ST ...

  5. Spark 生态系统组件

    摘要: 随着大数据技术的发展,实时流计算.机器学习.图计算等领域成为较热的研究方向,而Spark作为大数据处理的“利器”有着较为成熟的生态圈,能够一站式解决类似场景的问题.那你知道Spark生态系统有 ...

  6. Hadoop概念学习系列之Hadoop 生态系统(十二)

    当下 Hadoop 已经成长为一个庞大的生态体系,只要和海量数据相关的领域,都有 Hadoop 的身影.下图是一个 Hadoop 生态系统的图谱,详细列举了在 Hadoop 这个生态系统中出现的各种数 ...

  7. Hadoop生态系统如何选择搭建

    Apache Hadoop项目的目前版本(2.0版)含有以下模块: Hadoop通用模块:支持其他Hadoop模块的通用工具集. Hadoop分布式文件系统(HDFS):支持对应用数据高吞吐量访问的分 ...

  8. Visual Studio 2015的Web扩展包

    过去几年,Visual Studio扩展功能生态系统得到了蓬勃发展,社区贡献出了大量优秀的扩展,其中也包括大量针对Web开发的扩展.但是很多时候,感觉寻找.安装.更新好 几个扩展,总显得比较麻烦.如果 ...

  9. Hadoop 生态系统

    1.概述 最近收到一些同学和朋友的邮件,说能不能整理一下 Hadoop 生态圈的相关内容,然后分享一些,我觉得这是一个不错的提议,于是,花了一些业余时间整理了 Hadoop 的生态系统,并将其进行了归 ...

  10. Linux下使用fdisk扩展分区容量

    导读 我们管理的服务器可能会随着业务量的不断增长造成磁盘空间不足的情况,比如:共享文件服务器硬盘空间不足,在这个时候我们就需要增加磁盘空间,来满足线上的业务:又或者我们在使用linux的过程中, 有时 ...

随机推荐

  1. 【操作日志】如何在一个SpringBoot+Mybatis的项目中设计一个自定义ChangeLog记录?

    设计一个业务改动信息时的自定义记录,例如新增.修改.删除数据等.并且记录的规则可以通过配置的方式控制.大家需要根据各自业务场景参考,欢迎讨论.伪代码如下: 实体类: @TableName(" ...

  2. ChatGPT的原理与前端领域实践

    一.ChatGPT 简介 ChatGPT的火爆 ChatGPT作为一个web应用,自22年12月发布,仅仅不到3个月的时间,月活用户就累积到1亿.在此之前,最快记录的保持者也需要9个月才达到月活1亿. ...

  3. Python自学指南-第一章-安装运行

    1.1 [环境]快速安装 Python 与PyCharm "工欲善其事,必先利其器",为了自学之路的顺利顺利进行.首先需要搭建项目的开发环境. 1. 下载解释器 进入 Python ...

  4. CKS 考试题整理 (11)-沙箱运行容器gVisor

    Context 该 cluster使用 containerd作为CRI运行时.containerd的默认运行时处理程序是runc. containerd已准备好支持额外的运行时处理程序runsc (g ...

  5. .Net7发现System.Numerics.Vector矢量化的一个bug,Issues给了dotnet团队

    因为前几天做.Net7的矢量化性能优化,发现了一个bug.在类System.Numerics.Vector里面的成员变量IsHardwareAccelerated.但是实际上不确定这个bug是visu ...

  6. 云享·案例丨打造数智物流底座,华为云DTSE助力物联云仓解锁物流新“速度”

    摘要:华为云凭借领先的技术和快速响应的开发者支持服务,助力物联亿达实现云上资源高可用.提升系统安全性与稳定性,为物联亿达提供了扎实的数字化基础. 本文分享自华为云社区<云享·案例丨打造数智物流底 ...

  7. C++ 核心指南之资源管理(下)—— 智能指针最佳实践

    C++ 核心指南(C++ Core Guidelines)是由 Bjarne Stroustrup.Herb Sutter 等顶尖 C+ 专家创建的一份 C++ 指南.规则及最佳实践.旨在帮助大家正确 ...

  8. 【Redis】字符串sds

    sds,即 Simple Dynamic Strings,是Redis中存储绝大部分字符串所采用的数据结构. typedef char *sds; 一.类型 sds的类型包括SDS_TYPE_5, S ...

  9. 2023-07-13:如果你熟悉 Shell 编程,那么一定了解过花括号展开,它可以用来生成任意字符串。 花括号展开的表达式可以看作一个由 花括号、逗号 和 小写英文字母 组成的字符串 定义下面几条语

    2023-07-13:如果你熟悉 Shell 编程,那么一定了解过花括号展开,它可以用来生成任意字符串. 花括号展开的表达式可以看作一个由 花括号.逗号 和 小写英文字母 组成的字符串 定义下面几条语 ...

  10. 2023年ccpc河南省程序设计竞赛-clk

    很荣幸能够参加这次比赛,比赛机会挺难得得,还是第一次线下参加这样的大型比赛,比赛体验自然无话可说比较刺激..这次比赛我和队友crf和nhr共同解决了三道题,参与感极差,可以说问题很大,最简单的签到题我 ...