改善lsp-mode性能

全部细节见,

原来困扰我的问题是每次打开一个新的js文件时lsp-mode会让我等很长时间,现在这个问题已解决了。打开第一个文件还是要等待,但之后打开新文件速度就很快了。

我读了lsp-mode的代码,性能上还有很大改进空间。比如lsp-register-custom-settings本质就是一行代码操作一个list,但是特别慢。有兴趣的同学可以进一步优化。

另外lsp-clients实际上加了一打的clients,完全可以手动加入某个client,lsp-register-custom-settings也许可以不用。这样第一次加载lsp-mode也可以快很多了。

3 个赞
;; no real time syntax check
(setq lsp-prefer-flymake :none)

这个变量已经是标记为 obsoete 了,现在推荐用 lsp-diagnostic-package

4 个赞

懒猫 说要重建一个lsp-mode

seagle0128 是lsp-mode的参与者

其他人想要参与恐怕也是力不足

总是听说vim的lsp很快 neovim自带lua和tree-sitter

博客代码还需要改进。思路是对的,但是代码有问题。 lsp-auto-config不能为nil,牵涉到逻辑太多. (setq lsp-client-packages '(lsp-clients))对我就可以了,因为我只用javascript.手动(require 'lsp-clients)也没必要的

这其实是一个 trade-off 的问题。我参与了lsp-clients的讨论。最早其实是没有这个东东的,用户想用什么就装一个 lsp-xxx,然后自己配置。这时候很多人就出来说使用太复杂了,不如eglot,blablabla。。。

@yyoncho 就提出了这样一个改进,目标是 out-of-box,对标就是eglot。目前可以看到lsp-mode已经内置了大多数的 langserver,比 eglot 要多不少。不需要太多额外的配置就能跑起来,甚至很多 langserver 可以自动下载安装。从用户体验上说确实是降低了门槛,提高了易用性,对新手更友好。当然,对很多高手而言其实差别不大,比如 @redguardtoo 大佬可能并不需要。lsp-clients 中主要是对 langserver 的注册,真正启用是在打开相应的项目文件时。启动时加载肯定有一定影响,但根据我的使用体验看应该不大。当然,我并没有详细测试数据,如果影响真的很大可能需要提供一些信息、报个 issue 我们一起来看。

总体上讲,个人认为这还是利大于弊的。目前我的配置中完全采用了 lsp 方案,体验也很接近 VSCode。这也是 LSP 存在的价值和意义。后续 lsp-mode 还会在性能和功能上进行加强和优化。比如 @yyoncho 提到会有自己的 posframe 实现来替代目前的展示方案,还有 JSON 解析优化等等。另外,还有懒猫等提出的想用 rust 写一个 lsp client 然后集成到 emacs,这也是很好的想法。

lsp-clients 我觉得是没有做到按需加载吧,像org babel那样,第一次加载这么多无关的lang包,启动就很慢了。

(defcustom lsp-client-packages
  '(ccls cquery lsp-clients lsp-clojure lsp-csharp lsp-css lsp-dart lsp-dls lsp-elm
    lsp-erlang lsp-eslint lsp-fsharp lsp-gdscript lsp-go lsp-haskell lsp-haxe
    lsp-intelephense lsp-java lsp-json lsp-metals lsp-pwsh lsp-pyls
    lsp-python-ms lsp-rust lsp-solargraph lsp-terraform lsp-verilog lsp-vetur
    lsp-vhdl lsp-xml lsp-yaml)
  "List of the clients to be automatically required."
  :group 'lsp-mode
  :type '(repeat symbol))

lsp-client-packages 可以用默认的设置,定制这个变量只是略加速项目中第一个文件的载入速度。后面打开项目其他文件速度都很快了,如使用最新版lsp-mode的话。

这并不是加载所有功能,只是注册 clients,真正打开文件才会启动相应的server。而且,提供 lsp-client-packages 就给用户定制想要的 clients。

我明白只是注册clients,本质就是一行代码对一个list进行合并和uniq操作。但就是这一行简单的代码特别慢。原先是每次打开一个新文件Emacs都要卡几秒。在最新版接受了我的patch后好很多了,只是打开第一个文件时要卡几秒。

能做个profiling吗?看看究竟是哪个调用耗时。打开一个文件卡几秒太夸张了,这个应该解决。我的环境上没有这个问题

optimize lsp when it is called multiple times by redguardtoo · Pull Request #1498 · emacs-lsp/lsp-mode · GitHub 上有写测试方法和测试结果。可能我的电脑一直比较差,所以改进后速度提升还挺明显。

和我以前给awesome-tab的这个pr差不多: perf: remove unnucessary "require" expression by FirstLoveLife · Pull Request #64 · manateelazycat/awesome-tab · GitHub

1 个赞

没有意识到你说的是这个 PR。已经 merge 了,thanks!

我赞同你的观点

posframe展示是因为在一个issue里我录了个gif coc使用floatwindow的效果,问了下能不能在emacs用posframe,结果等了很久还没…

目前 lsp-ui 中已经支持 child frame,但还不是很统一。这方面还在讨论中,不过优先级确实没那么高。

另外一个可改善lsp性能的技巧。和emacs无关,见 Rename runs over node_modules folder? · Issue #394 · sourcegraph/javascript-typescript-langserver · GitHub 这个langserver假设用户用的是vscode,所以会去搜索jsconfig.json,读入其设置。可以用来设置可以跳过扫描的文件和目录

lsp-mode的lsp-file-watch-ignored不会影响langserver扫描,只是影响lsp-mode自己的变量。

我还没验证过,各位验证过的可以交流一下体验

1 个赞

又发现了一个优化lsp-mode的技巧,如果用js2-mode打开js文件的话,可以用其自带的imenu,因为js2-mode自带一个js parser,响应很快,不用等lsp的比较慢的parser返回imenu结果了。

(if (derived-mode-p 'js2-mode) (setq-local lsp-enable-imenu nil))
(lsp-deferred)
2 个赞

js2-mode 自带的parser不是增量分析的

tree-sitter可以增量分析

如果只是imenu的话 ctags不行吗?

ctags只能用正则表达式,解析不出上下文(如函数所在的class,promise). 另外imenu只解析当前buffer,要分析的数据很少,所以对parser的效率要求不高。我不用lsp的imenu功能是因为第一次扫描代码的时间太长,但我习惯一开始就用imenu看刚打开的代码,这样会卡很久。