schwaa
1
我目前在做一个文档阅读器,它需要把文档的每一页渲染成图片然后在emacs里显示出来。目前支持平滑翻页,就是能在一个窗口里同时显示连续的多个页面。因为它只渲染当前页面和相邻的几个页面,所滚动还算流畅。
但是我遇到个问题,就是每当窗口里出现一个未曾渲染过的新页面,emacs会卡顿一下,时间在0.1到0.2秒之间。这个时间接近dynamic module里的c函数渲染页面的时间。 为此我在c代码里加上了缓存,卡顿时间缩短到0.01到0.02秒之间。翻页的时候,体验明显改善,但是仍然能感受到短暂的的卡顿。
我猜想仍然存在卡顿的原因是emacs处理新图片的过程有点慢(或许跟image cache有关)。它见过这个图片之后,再显示起来会快很多。
我想如果能在显示一个页面的同时,预先加载后续的几页,卡顿现象应该能完全消除。这个加载过程必须用elisp做,让emacs把图片放到image cache里。
我尝试了 run-with-idle-timer 和 emacs-async。run-with-idle-timer执行我写的elisp函数时会crash,查看core文件可以看到c代码中出现了空指针。我不清楚run-with-idle-timer到底是怎么实现的,crash或许跟它的实现有关。emacs-async则完全不能识别我写的elisp函数,说符号未定义,估计也跟它的实现有关。
在c代码里执行elisp代码恐怕不行,我之前做其他事的时候发现在c代码里开线程使用emacs传过来的env和args也会导致崩溃。
我不知道现在还有什么可用的实现异步计算方法。
我对elisp了解不多,希望有热心道友能指点一下。谢谢!
1 个赞
cireu
2
正好我前几天在Emacs-devel上看过类似的问题。
https://lists.gnu.org/archive/html/emacs-devel/2020-10/msg01746.html
简单来说,按传统的方法,不能。emacs_env这个结构不是线程安全的,这和Emacs的一些设计缺陷有关系,所以不能在多线程环境下从module执行lisp code。
Emacs目前提供的唯一异步信号处理工具就是process-filter
. 因此,可以利用unix socket/web socket/或者28新加入的具名管道支持。通过这个信号通道发送一个byte给Emacs。同时在module里暴露类似(module-get-next-async-state)
的函数,让filter执行这个函数。从而在多线程环境下异步通知Emacs。
3 个赞
如果是 dynamic module 的话,里面可以开线程去做其它的事吗?
首先,先不讨论Emacs, 只说PDF加载图片有两种方法:
- 像Gnome PDF 阅读器那样,所有图片一次性全部用多线程读出来,边打开边读取,速度最快,因为都在内存,但是内存占用大;
- 像EAF PDF Viewer那样,只读取当前页上下两页放进缓存,这样会兼顾速度和内存占用
你的问题是,因为Emacs没有真正的多线程,没法让主线程(渲染图片)和子线程(读取图片缓存)来回快速切换以避免卡顿的问题,因为Emacs一个时刻只能运行一个线程,当切换到子线程后就会卡住主线程,反之亦然。
解决方法:
- 在Linux平台,直接用EAF吧,你想要的缓存算法和PDF阅读器已经实现了;
- 采用多进程的方式,Emacs启动子进程,子进程在后面读取图片缓存,Emacs通过进程间通讯,Emacs通过接受子进程传过来的文件名来加载图片,这种方法可以避免Emacs进程自己读取图片的卡顿。
如果你用第二个方法,在你写多进程通讯之前,我强烈建议你像写Demo程序,就是让Emacs读取一些列图片,看看翻页时Emacs读取新图片的时候卡不卡?
如果读取新图片的时候卡,你即使用多进程的方法依然无法解决你翻页的时候短暂卡端的问题,因为瓶颈就在于Emacs读取一个图片并渲染的周期超过 30ms 。
Emacs真正的瓶颈在于不是真正的多线程和Emacs自身的渲染引擎太慢导致的。
在Linux就用EAF吧,不但浏览器、 PDF阅读器异常流畅,即使看视频都毫无卡顿。
我折腾Linux下各种图形库的编程很多年了,放弃折腾Emacs的多线程吧,这也是我创建EAF的原因。
5 个赞
Youmu
10
求项目地址,观摩一下。
其实我也一直想写个 pdf-mode
,就是懒只创建了 repo 一直没行动
schwaa
11
我现在还在试验阶段,代码结构经常大变。这几天被老师捉去干活,几乎没什么进展。等基本功能完善了我再推到github上。
aeghn
12
楼主有没有考虑过像 EAF 类似的 reparent
方案?