GNUS with threads
Emacs users have an unreasonable longing for thread support, not the
make-thread
cooperative threading kind that we have, but the
concurrent execution kind.
We want the Emacs experience to feel smooth. Inputs should result in stuff changing instantly on the screen, no stutters.
Is concurrent executing threads the only way to make Emacs buttery smooth?
Why does Emacs freeze up?
I would guess that most of the time that Emacs is freezing up it does so by design. It's busy waiting for some kind of IO to finish (sub process or network)
Emacs slowdown is seldom CPU bound but IO bound.
This is an very common way of doing IO with with a sub processes or network connection in elisp:
(let (response) (send-data process data) (while (not (setq response (get-response process))) ;; Busy wait until `get-response' returns non nil (accept-process-output process)))
Most of the time Emacs is not struggling to eval some elisp code, its
busy waiting in these while
+ accept-process-output
constructs. A
place where all mouse and keyboard events are blocked, except the
mighty C-g
.
There are a couple of ways to solve this problem when doing IO in elisp.
Lets explore one of the ways together with the well know gnus
loading screen (or the command that makes Emacs freeze up for 10
second).
One hacky solution
Here is an stack trace post M-x gnus
; where Emacs is spending a lot
of time doing nothing but it's best to block keyboard events.
> (accept-process-output) (map-wait-for-response) (nnimap-finish-retrieve-group-infos) ... (gnus-get-unread-articles nil nil) (gnus-setup-news nil nil nil) (#f(compiled-function () #<bytecode 0xac4a9c051bacf2d>)) ... (gnus)
What if:
- The
gnus
command was executed on amake-thread
- And
s/accept-process-output/thread-yield
Then we could yield to the Main
thread instead of wasting flops and
blocking keyboard events in (while (not response))
. Good news is
that accept-process-output
actually does yield if there are other
threads.
Let me present the non blocking gnus
command variant
gnus-with-thread-polling
in Emacs proper with threads (the kind we
have):
(defun gnus-with-thread-polling () (interactive) (make-thread 'gnus))
with-thread-polling
should be able to wrap other IO bound gnus
commands.
Notes
Should I yank this into init.el
?
Sure, but I can't promise that it won't break anything.
Performance
gnus-with-thread-polling
will never be faster then gnus
. Did some
benchmarking but the response time for my email provider is nothing
but stable I can't say anything conclusive.
Disclaimer
This is not how I would have used make-thread
if I where to refactor
gnus
. But rather exploring condition-notify
in the filter
functions or just using callbacks from the filter functions instead.
MacOS
with-thread-polling
don't work on MacOS with Emacs GUI (worked fine with
emacs -nw
):
thread-yield
will always yield to main thread but main thread won't yield back tognus
thread until it gets a keyboard/mouse event. seens_select_1
insrc/nsterm.m
. (bug #70032)- Emacs crashes due to
gnus
changing the buffer name which in turn updates the ui bar thingy in an non main thread, which is not valid in MacOS.
(2.) can be solved with the following:
;; remove osx ui bar thingy (add-to-list 'default-frame-alist '(undecorated . t))
Profiling
But why does accept-process-output
not show up when M-x
profiler-start
? I have no idea but slap an advice around it with
benchmark-progn
and see for yourself.
Thanks
Thanks to tromey in #emacs
for enlightening me that
accept-process-output
yields.