emacs慢这锅不能是emacs自己背了

最近需要在windows上做游戏客户端的开发。但是发现doom对windows上的支持有问题,于是就决定自己去攒一套配置出来用。结果发现,在windows上竟然不卡?(当然,magit还是卡的。。。。。)

毕业2年了,我也不算像之前一样只能在公司打打杂了。在去年,我为公司写了一套游戏的后端框架(用go开发的一套actor并发框架),我也是能自己写架构的人了。

我就自己两年的积累谈谈emacs多线程和gc的问题。

在emacs 29之后,emacs任然不流畅的原因是什么

在我看来,如果emacs29之后,使用过程中还有卡顿问题。多半是插件自己实现的不到位。如果实现插件实现到位,emacs还是卡顿,你换到vscode多半也会卡。

emacs 28还是27的时候推出了native-comp,这个我之前又在reddit上看过一篇帖子说native-comp之后的elisp不会慢过python。也就是说 elisp自身在纯计算上不会拖什么后腿。

再说说gc的问题,将gc的阈值设的高点应该是基本操作了(实际我不用gcmh也不卡,我自己攒的配置就没改gc)。但是gc不是导致emacs不流畅的原因(甚至不是原因)。在我使用doom emacs的过程中,2个小时的时候之后查看gc的次数大概在200次左右。我从来没遇到因为gc导致emacs卡住1/2秒无法响应的问题,我觉得不流畅是我在编辑的时候一直不流畅。并不是因为gc介入了才不流畅的(这是我在使用doom的过程中的感受,运行在wsl上)。

如果你觉得emacs不如vscode那么流畅,我猜可能是你使用了evil。我在doom上是开了evil使用的,由于需要debug我习惯使用vscode,所以有时候也会在vscode上写一些代码。不得不说,用evil的时候,和vscode的差距真的挺大的。

从前年我就知道evil这个插件的性能不是很好(如果和vscode比,一定的),主要体现在编辑的流畅性。于是我开始找别的模态编辑插件,试过viper, meow, 发现很流畅,可以说不输vscode。

插件实现不到位有两个参考点

  1. 插件自己的算法不ok, 有可能选了性能不好的实现方式,如果不是实现方式问题。估计别的编辑器在同样的feature下性能也好不到哪里去。
  2. 如果是依赖别的进程的话,可能使用了同步的方式,没异步。

关于emacs多线程的问题

我认为,emacs多线程可以暴露给emacs的核心开发者。但是不应该暴露给普通的插件开发者。emacs实际上也是这么做的,这么多年不暴露给插件开发者一定是深思熟虑结果。

  1. 处理过框架的,尤其是多线程框架。你应该明白多线程这个东西即使专业的程序袁也不一定能妥善的去处理。这种多线程的东西一般是框架去做的事情,如果你写业务的时候还要考虑“锁”,“线程”这样的东西,框架本身就是不合格的。在我编写框架的过程中,我都是极力去隐藏锁,隐藏线程的概念。让写业务的人,只需要用同步的思维去编写代码。没有别的原因,只是这样写业务最方便,bug少,多线程操作同一块内存导致的panic, 以及异步代码必须面临的callback在被调用时,引用的变量被别的异步代码块改变的问题。在公司的商业环境中,为了保证程序的稳健尚且需要考虑这么多问题。如果多线程暴露给水品参差不齐的开发者(甚至有些插件的开发者都不是专业的程序袁),势必是一个灾难。那个时候,你极有可能遇到插件之间配合不好,频繁导致emacs崩溃的问题。

  2. emasc本身其实就是多线程的,只是emacs强制你在编写插件的时候,只能用单线程+异步的思维去编写插件。事实上,我可以负责的告诉你,node也是这么做的。最近在用ts去写游戏前端,node所谓的多线程就是多起一个node的虚拟机。

要求emasc在插件层级支持多线程,就是一个不可能实现的梦。有可能实现的方式是,另外去起emacs的虚拟机去做耗时计算,完了再同步给主emacs去显示。你想不做任何处理跨线程操作变量?别做梦了。一个不加载任何插件的emacs启动后大概是50m左右,node是44m。多开完全不是问题,没有多线程用真不是emacs的问题。你可以多开emacs解决单一线程计算耗时的问题。这种需要消耗性能的插件一般都需要更专业的人去编写了。

其实在我的框架之前,公司的框架在逻辑上也是纯单线程的(游戏的后端,也完全够用了)。 对于会频繁遇到阻塞的程序,处理好阻塞=解决80%的问题。重要的是有没有异步机制,如果异步机制也解决不了问题才需要考虑上线程,这是在因为在开发效率和性能之间必须得有一个平衡才行。

13 个赞

doom卡应该不是evil的问题,而是doom的各种lazy load魔法搞过头了,导致用户使用的时候不停的在加载东西,当然evil比较重也是真的,evil基本上算是一个vim发行版了。。。

1 个赞

你可以对比下原生键位,会慢一点的

我用过1年左右的meow,感觉meow和evil性能差别不大,,和原生的倒是没对比过,因为我根本不会原生键位 :joy:。顺便问下viper是怎么处理special-mode buffer的问题的?需不需要类似evil-collection之类的改键位包?

viper要你自己去映射吧,没evil那么大众生态不是很好

啥时候能把我那个补丁合了就好了

赞同楼主的部分观点,也欣赏楼主的年少有为,不过不太清楚楼主在这个高手云集的论坛中好为人师之措辞的底气从何而来,不如楼主晒一晒手上的 Emacs/Lisp 项目让大家见识一下究竟是何方神圣?

1 个赞

语气不对,我道歉。不过node确实是这么做的,这是事实。想毫无成本的去跨线程操作变量也是不可能的,也是事实。或者用锁,或者用队列这种机制。总之,这种问题已经需要理解编程模型的人去处理了,也就是说带来了成本,需要额外的处理。

草,过年的时候我试试

看了一下,有些名字可能得改改,比如 MY_ 作前缀的名字

2 个赞

说到windows上emacs慢,你可以去看我之前发的贴子,有详细分析。 最大的原因就是linux上很多机制,windows上没有,只能模拟实现,必然会比linux上要慢。

另外你说的这些有对的也有不对的,都是论坛里讨论过很多次的问题,建议你先搜索读读相关的帖子。特别是关于emacs多线程的问题。emacs底层的 c 实现有线程没错,但是他的elisp是运行在单线程上的,这意味着界面和计算都在一个线程上,计算多了,界面必卡。

2 个赞

这几天在玩UE,难得用windows的emacs干重活。

感觉明显卡的功能是 dabbrev-other-buffer ;解析json;前者关了,json用个emacs-lsp-booster,补全UE够用

反正elisp和ui跑一个线程上是最大的问题,这个改善了就不会有卡卡的感觉。


题外话,UE真重啊,也可能是我写crud写小脑萎缩了,想业余玩玩看了好几天只能做出个连demo都不算的东西

这个改善不了,现在只能把计算多的地方放到外部进程去,类似lsp-bridge这样比较现实。就是很多有洁癖的人不能接受

1 个赞

如果涉及linux上的机制,是会慢的。如果只是纯计算,我觉得差不多,今天测了下fib,wsl和win下是很接近的。业务线程也不是说必须单线程,前提是得能够合理的去拆分。unity这种引擎也支持多线程,但是本身的渲染线程/逻辑线程不允许别的线程直接介入。laya这种引擎是因为node本身就是逻辑单线程的,io部分由libuv接管(大概是这么个意思)。io那边可以开线程等待完成后,再同步逻辑线程。emacs本身其实也是这个意思,不开放多线程,我觉得没毛病。如果真的卡,也应该考虑将繁重的业务拆分到别的线程去。涉及到拆分业务的话,就需要插件编写者了解多线程的模型,这门槛就已经比较高了。理解的好,也不一定能处理的好,会引入风险。

简单来说,多线程能够带来的效果很明显。但是缺点也很明显,属于双刃剑。如果是正面,当然皆大欢喜。如果是负面,可能我们就接收不了了。

而如果不爽没有多线程,确实就可以像猫大那样用别的进程解决问题。但是我觉得这正是这个事情的最优解。既避免了提供多线程可能导致emacs崩溃的问题,也让有能力自己开进程解决问题的人有活路走。也就是说,自然会有更专业的人出来解决这个问题。

老实说我觉得公司的windows下比我自己的linux下流畅的。但是不习惯windows下的开发环境,所以还是自己带了一块rock pi 5b当单板服务器用。目前让我最难受的是开X forward的情况下因为网络不稳定导致的卡顿。

至于多线程什么的,elisp有多线程支持,只是缺乏必要的调试手段,测试困难。Emacs里面用的更多的是多进程IO。本来在linux下面就不分进程和线程的。

emacs那是协程的实现,不是并发线程。

emacs之所以多线程不好是大多数emacser根本就没写过图形多线程,然后不懂图形编程,分不清楚协程和并发线程,还喜欢抬杠好为人师哇,悲哀啊。

有空可以好好读一下我写的原因吧,New Emacs 构想 - #66,来自 manateelazycat

1 个赞

可能我说的让他们误解了, emacs的的其他线程应该是没大面积接触到emacs逻辑层面的处理的。定时器那些异步的东西其实也是在主线程里面跑的。是lua/c++现在的那种无栈协程吧,只是将当前线程的资源尽可能的利用不让它空闲而已,实际上真正做事的还是那个线程。

单纯吐槽一下言辞,没有说你这部分说的有问题,不过你说的 Emacs 29 之后慢是插件作者的锅这点就有点不敢苟同了,比较怀疑楼主是否有真正写过 Emacs 的插件:

  1. Emacs 本身就是提倡单线程的,不存在暴露多线程给插件开发者的情况,因为内置的基本函数压根就不标明其是否是线程安全,尽管提供了 thread 库,先不论其是否是真正的多线程, thread 库出来这么久了,插件用到它的地方也基本只在 I/O 上。

  2. 不说插件, Emacs 内置的代码都有很多是这样的:

    (unwind-protect
        (progn
          ;; 改变某个全局状态
          ;; ...
          )
      ;; 恢复某个全局状态
      )
    

    虽然看上去比较 Tricky ,但在单线程的 Emacs 下都是可靠的代码。

  3. Emacs 的标准文本处理逻辑,可以说都是在 save-excursion 中用一系列命令在缓冲区内移动 point , 执行对应进行编辑或查询,这虽然很优雅,但是本来就不快,因为各种琐碎的光标移动命令之间无法复用中间结果,永远不知道一条简单的光标移动命令的背后包含着多少次的正则表达式匹配。

1 个赞

本质是emacs插件包袱是不可能抛弃掉的,抛弃不掉,你再这么革新多线程,没有插件也没用。

要支持现有插件,多线程根本没法支持,elisp那么多状态,那么多hook,那么多macro,没法多线程管理。

唯一的方法就是往外部进程卸载计算。