reddit / emacs
Tips for Modeline Performance
tangonov · 2022-09-16 00:27
I'm having a rough go at making my bespoke modeline. It's gotten a lot better in performance but now I'm noticing a "creeping" performance leak.
It's most noticeable when I tangle org files or open up my agenda. Both of these operations get progressively slower and slower until I have to wait several seconds just to have a look at my task list.
I went into my config and commented out my modeline setup. Without even waiting for things to get worse I noticed these operations were way snappier.
I've been told that:
1. Avoid doing evals in your modeline
2. Avoid expensive functions if you are doing evals
I don't believe any of my function calls are expensive per-se. I am doing some weird stuff with cached git statuses and I may be rendering an SVG icon for god mode. I do have god-mode on by default.
I'm guessing that somewhere in the background lurks a billion ever-exapanding buffers, temporary or otherwise, that are needing to eval their git project status and render an svg icon.
Anyway, at this point I'd like to distill my modeline down into simple, colorized text. Maybe with a gap between left and right sides (already got this). I'd take any advice I can get.
Link to modeline lisp: https://paste.debian.net/1254030
-----
**Update:** Thanks so much for all your help, guys! After much refactoring I was able to do a day's work without needing to dump my session due to mode-line blunders.
The general idea behind how I was able to make the necessary improvements are that if I must eval, keep the eval functions as simple as possible and use as many pertinent hooks and advice functions to cache the mode-line elements.
I got my evals down to mostly just retrieving stored, pre-computed variables and am pretty happy.
7890yuiop · 2022-09-16 00:33
> Both of these operations get progressively slower and slower until I have to wait several seconds just to have a look at my task list.
Without seeing code it's impossible to say, but it *sounds* like you're inadvertently processing ever-larger quantities of data over time, so I'd hazard a guess that you're *adding* new things to your mode line data rather than *replacing* old things.
> I'm guessing that somewhere in the background lurks a billion ever-exapanding buffers, temporary or otherwise, that are needing to eval their git project status and render an svg icon.
I think not unless they're being displayed in a window -- they have no mode line otherwise.
> I'd take any advice I can get.
Just the same advice you've seen already -- try not to eval things in your mode line. A better approach is often to simply include a *symbol* in your mode line as a variable, and assign the pre-evaluated/rendered mode-line content to that variable. You can then perform *updates* to that variable outside of the mode-line itself (using whichever logic makes sense, but hopefully in a way which gets called dramatically less often, and only when necessary). YMMV -- it depends on what you're actually wanting to show.
tangonov · 2022-09-16 00:38
It's awful. But here it is https://paste.debian.net/1254030
lebensterben · 2022-09-16 01:10
should try to make vc (and probably others) asynchronous.
see earlier discussion: https://www.reddit.com/r/emacs/comments/lrp616/async_git_modeline/
7890yuiop · 2022-09-16 01:53
Right, there's plenty happening there. Look at every single piece of information and ask yourself whether that data *needs* to be re-calculated each and every single time Emacs needs to redraw the mode line (which happens **a lot**), or whether you can instead calculate that thing only when it changes, using some hook or other process, and just store the value then (using buffer-local variables for the values which will be buffer-local).
There's going to be a way to react to flycheck finishing its processing, so you could calculate that value one time at that point.
Your cached git stuff... it's your own cache, so you could simply generate the value only when you update the cache, rather than repeatedly every time the mode line is redrawn.
Your icon function is... extracting an icon from some collection of SVGs? and propertizing it... in exactly the same way... and always for the same hard-coded icon... every time the mode line is redrawn. Just put the pre-processed result it in a variable.
You're doing a bunch of `project`-related processing each time (and sometimes calling `project-current` twice). Are your files ever changing their project from one mode line refresh to the next...?
You get the idea. If you don't do more than you need to, you don't need to worry about how often it's happening, because the answer is "only as often as necessary".
tangonov · 2022-09-16 02:01
I have heard of asynchronous operations in Emacs but I have not yet explored them. Honestly didn't think I'd need them so long a-oh crap 3000 buffers in this project! How!?
tangonov · 2022-09-16 02:34
Seems pretty reasonable to me. I never viewed many of these operations as that expensive and therein lies my first mistake. The default modeline is all variables. I will try to do the same.
7890yuiop · 2022-09-16 03:21
Note that you may need to do this in some cases:
(put 'my-mode-line-var 'risky-local-variable t)
Refer to:
* `C-h i g (elisp)Mode Line Data`
* `C-h i g (elisp)Properties in Mode`
tangonov · 2022-09-16 06:23
I refactored the git status and icon so far. I am noticing improvements already and far fewer anomylous project buffers.
This has been very helpful. Thank you!
crundar · 2022-09-16 07:10
This is all great advice. Honestly can't believe this is the kind of service some kind soul willingly gives out for free.
redguardtoo · 2022-09-16 08:53
Check my modeline setup which is optimized for high performance. https://github.com/redguardtoo/emacs.d/blob/master/lisp/init-modeline.el
I set up 3rd packages to make sure they do not mess with the mode line.
```
(with-eval-after-load 'time
;; Donot show system load in mode line
(setq display-time-default-load-average nil)
;; By default, the file in environment variable MAIL is checked
;; It's "/var/mail/my-username"
;; I set `display-time-mail-function' to display NO mail notification
(setq display-time-mail-function (lambda () nil)))
```
You could search my emacs.d git log for all the optimization techniques
7890yuiop · 2022-09-16 12:00
That's nice of you to say, thank you. I think that Emacs has a pretty great community in general -- whether providing help to others, writing and sharing elisp, or working on the next release of Emacs itself, there are a lot of people giving their time to help improve other people's experience with our favourite editor.
arthurno1 · 2022-09-16 14:54
(setq-default mode-line-format
'((:eval
(tdm/split-format
;; Left
(format-mode-line
'(" "
(:eval (tdm/meow-state))
(:eval (tdm/status-flag buffer-read-only 'error))
(:eval (tdm/status-flag (buffer-modified-p) 'warning))
(:eval (if (not (eq
(format-mode-line mode-line-client)
""))
(tdm/status-flag t '(:foreground "#C792EA"))
""))
" "
(:eval (tdm/project))
mode-line-buffer-identification
(:eval (tdm/buffer-size))
(:eval (tdm/buffer-position))))
;; Right
(format-mode-line
'((:eval (tdm/god-state))
(:eval (tdm/macro-indicator))
(:eval (tdm/flycheck))
(:eval (tdm/vc))
(:eval (tdm/misc))
" "
mode-line-modes))))))
I am not an expert in this, but seems like you are calculating all those states on every modeline update? Can you add a mode hook to each mode to update its modeline string after you load a particular mode? Similar as how you would diminish them? Most of those seems to be minor modes, so check minor-mode-alist variable and with-eval-after-load to do things after you load a package.
tangonov · 2022-09-16 15:13
Yeah for sure. 7890yuiop made the same observation in our conversation on this post.
The challenge with crafting a mode-line is knowing which functions are ok and which are not. That snippet you've captured was borrowed from another user who boasted having a lightweight alternative to doom-modeline. They also used a bunch of evals.
I think from now on I just won't use evals at all. If it seems like too much work to figure out advice functions or hooks I don't really need whatever it is.
The only downside to storing segments as fixed variables is the calculations for the modeline gap get thrown off with the svg element. Ah well.
arthurno1 · 2022-09-16 15:31
> The challenge with crafting a mode-line is knowing which functions are ok and which are not.
When doing programming, the fastest computation is one you don't do at all :).
For example, I would think twice if I need to take git status on all files for the modeline? Even if cached. Anyway, I don't know how vc-* stuff works in regard to git, but check https://github.com/romkatv/gitstatus project, and see if it helps.
Next fastest is one you can do at the compile time, then one you can do when the program starts. Finally, if you have to recompute things dynamically to reflect some frequently changing state or important changes, then you do it at runtime.
Here your "program" is a mode being loaded, so you should be able to calculate and set its lighter in with-eval-after-load, unless you don't want to reflect some state that changes dynamically. I have just skimmed over your functions, and at least on a shellow look, it seems that you are just computing some different lighters, but you do check on every modeline update to see if modes are on or off. You know that mode is on when you activate it, so no need to check on every update, just eval new lighter when you load that package, or at least only in mode-hook, after mode is loaded.
> The only downside to storing segments as fixed variables is the calculations for the modeline gap get thrown off with the svg element.
It is at least 8-10 years since I last tested to display SVG in mode line. Back than I founded it too slow and never missed it again, and I have always had relatively beefy computers I run Emacs on.
tangonov · 2022-09-16 16:10
> When doing programming, the fastest computation is one you don't do at all :).
Of course. To be fair it was all working fast for the first 30 minutes of usage xD Jokes aside I put a (message) in an update funciton just for kicks and watched it fire in the message buffer. It demonstrated how often the modeline updates really well.
> It is at least 8-10 years since I last tested to display SVG in mode line. Back than I founded it too slow and never missed it again, and I have always had relatively beefy computers I run Emacs on.
It might be worth re-visiting. I am pretty sure my outrageous number of temporary project buffers had something to do with `svg-lib` but since storing the computed svg in a variable it's worked really well.
redblobgames · 2022-09-16 17:19
In addition to making the modeline cheaper, I also noticed that sometimes my modeline was being update a *lot*. I set up a counter:
(defvar-local mode-line-eval-counter 0 "Debugging :eval in mode-line")
and then I put this into my modeline config:
(:eval (progn
(setq mode-line-eval-counter (+ 1 mode-line-eval-counter))
(format "%+4d" mode-line-eval-counter)))
Eye opening! :-)
I also followed the advice of others here in moving some computations into (buffer-local) variables so that they could be computed just once, or maybe once in a while.