讨论一下lsp-mode作者在emacs里实现的异步非阻塞json-rpc

lsp-mode 作者意识到只改 lsp-mode 的代码无法提高多少性能,因为 emacs 的 ui 线程busy 时不会做任何 io 。他基于 28.1分支修改了 emacs 代码,使用独立线程跟 lsp 服务器进行 json-rpc 通信,结果是让 emacs 的 UI 线程减少了 95% 的负载,同时也改善了 GC 的压力。

原帖:https://www.reddit.com/r/emacs/comments/ymrkyn/async_nonblocking_jsonrpc_or_lsp_performance/ 作者修改的emacs的仓库地址:GitHub - emacs-lsp/emacs: Mirror of GNU Emacs

尝试的话,使用上面的仓库地址编译emacs,记得加上 --with-modules 这个flag。

有没有朋友试过?有没有可能把这个改动合并到emacs主线?

9 个赞

看了代码 [POC] Initial implementation · emacs-lsp/emacs@044555a · GitHub

本质是用了子进程来解析lsp返回的json信息,和独立线程没关系,那个线程相关的代码只是定时轮训来获取子进程的数据。

这个架构基本上就是lsp-bridge的思想之一,之前很多用lsp-mode的人嘴硬,打死不承认慢,lsp-mode作者现在做出了改变,好事。

剩下的慢就是基于 capf 的 company和corfu 在超多返回的时候, 因为 capf 的设计缺陷而导致的每次刷新菜单都要重新搜索上千个候选词导致的慢。

独立进程就是 lsp-bridge 在 python 端做的事情, 如果lsp-mode把补全前端也按照 LSP 的协议重新实现, 就是 lsp-bridge acm 做的事情。

10 个赞

这基本就是lspce的实现逻辑啊,不说一模一样也差不多了,希望能合并。

1 个赞

刨除capf,好像跟lsp-bridge更像,子进程加callback的方式,lspce里没用callback。

其实像谁都无所谓,主要是大家要理性分析,承认elisp性能和emacs GC不适合超大数据的应用场景。

承认不足才能利用他山之石更好的改进emacs,而不是像很多emacser一样一边抱怨卡,一边还原教旨主义的坚持什么代码都要elisp实现,或者在emacs中大量写c轮子都不使用外部库。

3 个赞

为什么几乎没有人使用emacs29的thread机制呢?

刚看了下readme,并没有说需要--with-modules,而且就实现方式来说,也不是用的module,并不需要为这个json-rpc开这个flag。

可能还是限制比较多吧。看了下lsp-mode为了支持这个非阻塞json-rpc做的修改,倒是看到个make-thread

原贴说的

看这个帖子内容,不像是异步json-rpc的作者发的。可能是发帖这人也不太清楚?我看过修改的代码,看着是不需要啊。

不过这是个小问题,好多人自己编译Emacs,这个flag也都打开了。

如果能合并到Emacs里,eglot也会受益。我可能也就不继续维护lspce了。。

1 个赞

发帖人就是 lsp-mode 作者 yyoncho

可能他确实忘了不需要这个flag,可以去原贴问一下

哈哈,尴尬了,我这儿打不开reddit,所以瞎猜了一下发帖人不是作者。抱歉抱歉,水了这好几贴。

关键是这种大家喜闻乐见的改进确实就是在emacs里用c实现的,也只有这种改进才能被大家接受,才有可能合并进emacs主线。emacs社区就是不喜欢用什么python typescript deno来扩展,用racket guile sbcl甚至还好一些。lsp-bridge的架构确实是优秀的,但选择了让很多emacs用户和开发者难以接受的实现方式。

5 个赞

从 Emacs 27 开始就不需要加 --with-modules 这个 flag 了,默认是打开的,加不加都是打开的。

1 个赞

确实没专门去注意过这点,感谢指正。

我来翻译一下很多 Emacser 这种病态的想法:

  1. 希望所有代码都是 Elisp 写的, 即使Elisp在某些时候比别的语言慢几十倍甚至上百倍, 但是就是Elisp实现的才是香
  2. Elisp可以通过 make-process 的方式调用外部程序,然后通过命令行输出的方式写一大堆正则过滤hacking way风格、不可维护的代码, 也不用外部语言 + RPC的方式, 即使RPC的方式是语义逻辑去精确控制
  3. Elisp本身线程实现的方案就不是真并发多线程, 最多就是独占式的协程, 每个协程轮流切换, 遇到强力计算就卡住其他线程, 这种方案写代码一直要各种手写锁住/解开线程的代码, 即使这个世界几乎所有语言都有实践检验的多线程方案, 还是要用这种最蹩脚的独占式协程方案写代码
  4. 世间有那么多语言的开发库可以用, 可以好好的把各语言的 FFI 做的好一点, 而不是开发者手动做类型转换, 不用 FFI, 而是啥都要用C语言写,然后期待所有开发者用C语言去维护所有平台的差异化细节
  5. 很多流行生态好的语言不用, 比如 Python, JavaScript, Golang 等等, 现成的健壮语言和生态不借力, 反而是用 Lisp 风格的语言去扩展才是香, 即使生态库差距上万倍? 一个库一天可以弄完, 另外一个类 Lisp 的语言, 吃饭要从种水稻开始弄才是香?
  6. 用类似 Github 的方式讨论, 做Code Review才是更开放的方式, 但是什么东西都要用邮件列表, 给Emacs贡献代码不是看代码的方案, 而是看开发者是否有时间泡邮件列表? 很多有用的东西, 不是开放式接纳现有生态, 而是啥问题都要看 政治正确

lsp-bridge从来就不希望进Emacs分支, lsp-bridge类似的方式就是想通过实践告诉大家, 借用RPC的方式可以快速进化Emacs而不用啥都要修改Emacs源代码才能实现。

可惜的是, lsp-bridge带来的性能提升明显的现实情况下, 很多人装作看不见, 很多人欺骗自己lsp-mode性能还行, 现在 lsp-mode 的作者自己做出了改变, 多好的事情, 终于解析 JSON 数据性能提升了, 很多人说, 我就喜欢 C 的实现, 特别是 Emacs C 实现最香。

外部语言 + RPC 的方式可以有效的替代 “Emacs + 外部命令行 + 正则过滤”, “Elisp性能严重不足的巨型数据分析的应用场景“, “真正多线程并发的图形展示” 和 “Emacs 需要写大量C语言维护代价极大” 等几个场景, 不是说Emacs用了其他语言, Elisp语言和Emacs C库就不能用, 这根本不是谁好谁坏的方案, 是可以共存的方案, 合适场景用合适的方案去扩展Emacs, Emacs才能百花齐放。

上面是我对这个帖子的最后回复, 详细的想法已经列举很清楚了, 为了不陷入无谓的争论, 我会忽略此贴, 因为我很清楚, 很多事情永远都叫不醒装睡的人。

18 个赞

一针见血,非常赞同。

1 个赞

实现简单、安装配置方便、代码可读性好就ok了,管它什么语言实现的,不是非要用elisp,适合自己,好用即可。

2 个赞

我觉得用什么语言扩展Emacs 都很好,尤其是发挥不同语言的生态优势。不过,我倒是很同意你前面的一句话,如果这种改进,可以合并到Emacs的主线,那会让大家受益很多。

1 个赞