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
gnuscommand 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-yieldwill always yield to main thread but main thread won't yield back tognusthread until it gets a keyboard/mouse event. seens_select_1insrc/nsterm.m. (bug #70032)- Emacs crashes due to
gnuschanging 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.