domtronn / all-the-icons.el

A utility package to collect various Icon Fonts and propertize them within Emacs.
MIT License
1.48k stars 177 forks source link

[SVG] Main performance issue #369

Open thierryvolpiatto opened 1 year ago

thierryvolpiatto commented 1 year ago

Hello, following up from reddit (helm05).

I made all the changes to make Helm working with this all-the-icons branch. The main issue I see is a loss of performance because the all-the-icons* functions return now a space with an svg image as display property. Here from an helm-imenu function: Capture d’écran_2023-06-19_07-49-23

Now printing the helm-imenu for all buffers (around 100) takes more than 30s whereas with master branch it is nearly instant.

wyuenho commented 1 year ago

I'm not entirely sure why the difference is so high. Is it because you are running Emacs under WSL? If not, can you provide me with a profiler report?

thierryvolpiatto commented 1 year ago

Jimmy Yuen Ho Wong @.***> writes:

I'm not entirely sure why the difference is so high.

My guess is that there is too much properties to compute at each turn of the loop and there is a lot of candidates. In previous version (master) there is not so much properties to compute. If you want to try all-the-icons on Helm with both versions (svg and master) you can checkin the Helm svg branch, it is working with both branches of all-the-icons, icons are enabled in helm-find-files with helm-ff-icon-mode, in helm-buffers-list with helm-buffers-show-icons and in helm-imenu with helm-imenu-use-icon (yo can hide types with helm-imenu-hide-item-type-name). It is easy to see the difference of speed with both branches of all-the-icons with M-x helm-imenu-in-all-buffers. Also on helm-find-files I can see a slowdow on large directories.

Is it because you are running Emacs under WSL?

My OS is LinuxMint, don't know what is WSL.

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you authored the thread.*Message ID: @.***>

-- Thierry

thierryvolpiatto commented 1 year ago

Thierry Volpiatto @.***> writes:

Jimmy Yuen Ho Wong @.***> writes:

I'm not entirely sure why the difference is so high.

My guess is that there is too much properties to compute at each turn of the loop and there is a lot of candidates.

More exactly, my understanding is that #(" " ... display (... :data)) takes much longer because Emacs have to setup the image according to (huge) raw data every time an icon is displayed.

-- Thierry

thierryvolpiatto commented 1 year ago

So no, this is not the reason, it is the repeated calls to locate-library which make things so slow, with this change, I get almost the same performances as before:

diff --git a/all-the-icons.el b/all-the-icons.el
index 671037e3b..7f78fd268 100644
--- a/all-the-icons.el
+++ b/all-the-icons.el
@@ -1100,6 +1100,7 @@ Return the icon file name if found."
                    (fn (all-the-icons--data-name (car mapping))))
           (assoc-default (cadr mapping) (funcall fn))))))

+(defvar all-the-icons-svg-library "/home/thierry/elisp/all-the-icons.el/svg/")
 (cl-defmacro all-the-icons-define-icon (name alist &key svg-path-finder (svg-doc-processor ''identity) (padding 0))
   "Macro to generate functions for inserting icons for icon set NAME.

@@ -1125,7 +1126,7 @@ PADDING is the number of pixels to be applied to the SVG image."
      (defun ,(all-the-icons--function-name name) (icon-name &rest args)
        (let* ((file-name (all-the-icons--resolve-icon-file-name icon-name ,alist (quote ,name))) ;; remap icons
               (size (window-default-font-height))
-              (lib-dir (concat (file-name-directory (locate-library "all-the-icons")) ,(format "svg/%s/" name)))
+              (lib-dir (concat all-the-icons-svg-library ,(format "%s/" name)))
               (image-path (concat lib-dir ,(or (and svg-path-finder
                                                     `(apply ,svg-path-finder file-name lib-dir size args))
                                                '(format "%s.svg" file-name))))

This is of course a dirty patch just to show where the slowdown happens.

thierryvolpiatto commented 1 year ago

So probably computing the path of the svg directory one time for all is the way to go.

thierryvolpiatto commented 1 year ago

So even if avoiding locate-library make things much faster, I still see some slowdowns due to stuff being computed at each of the function calls, probably these could be avoided or at least cached (memoized)?

wyuenho commented 1 year ago

@thierryvolpiatto see if eb32540d4288bb66e0b78313ac0c4f1f45c34268 works better.

About cache, the icons are cached at here. Caching them at the icon set level is going to blow up memory usage tremendously, and introduce more cache invalidation issues I have to deal with in addition to #367 and #368.

Let me know if you can think of other ways to speed up this branch :)

thierryvolpiatto commented 1 year ago

Jimmy Yuen Ho Wong @.***> writes:

  1. ( ) text/plain (*) text/html

@thierryvolpiatto see if eb32540 works better.

Yes, it is the same patch I used yesterday except defvar vs defconst, around 30s faster on M-x helm-imenu-in-all-buffers!

However it is still not as fast as with master branch, we still have a lot of work done at each icon computation.

About cache, the icons are cached at here. Caching them at the icon set level is going to blow up memory usage tremendously, and introduce more cache invalidation issues I have to deal with in addition to #367 and #368.

I see. I was thinking more of the usage of this:

(apply ,svg-path-finder file-name lib-dir size args)

and this: all-the-icons--normalize-svg-doc

Let me know if you can think of other ways to speed up this branch :)

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you were mentioned.*Message ID: @.***>

-- Thierry

thierryvolpiatto commented 1 year ago

Jimmy Yuen Ho Wong @.***> writes:

Let me know if you can think of other ways to speed up this branch :)

I am now caching helm-imenu icons, it working very well, M-x helm-imenu-in-all-buffers is now instant :-)

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you were mentioned.*Message ID: @.***>

-- Thierry

thierryvolpiatto commented 1 year ago

Jimmy Yuen Ho Wong @.***> writes:

About cache, the icons are cached at here.

Yes but this cover only:

(all-the-icons-cache #'all-the-icons-icon-for-dir) (all-the-icons-cache #'all-the-icons-icon-for-dir-with-chevron) (all-the-icons-cache #'all-the-icons-icon-for-file) (all-the-icons-cache #'all-the-icons-icon-for-mode) (all-the-icons-cache #'all-the-icons-icon-for-weather)

Caching them at the icon set level is going to blow up memory usage tremendously, and introduce more cache invalidation issues I have to deal with in addition to #367 and #368.

Yes, but why caching them all, why not caching them on demand? E.g. Say I have a list of files:

foo.diff bar.diff baz.diff

And I want to append an icon to each file, all the files have the same type so on foo.diff I use (all-the-icons-octicons "file-diff"), then if this is cached next call on bar.diff will reuse the previous result.

Actually we have:

(defun all-the-icons-octicons (...) (compute-icon))

We would have instead

(defun all-the-icons-octicons (...) (if cached (use-it) (compute-icon)))

Probably only the result of (function name) would be cached and the &rest args e.g. 'face ... would still be computed.

Let me know if you can think of other ways to speed up this branch :)

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you were mentioned.*Message ID: @.***>

-- Thierry

thierryvolpiatto commented 1 year ago

Jimmy Yuen Ho Wong @.***> writes:

@thierryvolpiatto see if eb32540 works better.

About cache, the icons are cached at here. Caching them at the icon set level is going to blow up memory usage tremendously, and introduce more cache invalidation issues I have to deal with in addition to #367 and #368.

After digging into this I realize that the caching you provide by transforming fns with all-the-icons-cache makes entries in the hash table with keys composed of the whole call of function with args (fname etc...) and comparison is done with 'equal. That's mean that each filename have its own icon composition in the cache! When providing icons in a huge directory containing files of the same type (e.g. backup dir) your cache is quickly full filled. Now on Helm I have modified this to cache icon for file by extension, type or default so that next file reuse the cached version of the precedent (if it's the same kind of file). This limit the usage of memory, the cache is never cleared and BTW it is much faster.

Let me know if you can think of other ways to speed up this branch :)

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you were mentioned.*Message ID: @.***>

-- Thierry

thierryvolpiatto commented 1 year ago

For reference: https://github.com/emacs-helm/helm/commit/4a540f5cdda0fc1c26bdb0f4c84706108b7e6246

thierryvolpiatto commented 1 year ago

Now helm is working fine with this branch, by using caching where needed I have reduced memory usage and increased speed. When do you plan merging this branch to master?

wyuenho commented 1 year ago

Don't know, whenever I have some time and brain space :) Feel free to send over a PR if you'd like

thierryvolpiatto commented 1 year ago

Jimmy Yuen Ho Wong @.***> writes:

Don't know, whenever I have some time and brain space :)

Ok, looking forward for it :-).

Feel free to send over a PR if you'd like

We have first to agreed on the issues there is to fix. Here is one (related to this performance issue):

The wrapper fn all-the-icons-cache used to cache icons is taking too much memory and make BTW the functions transformed by it slow unless increasing (a lot, by X4 here) all-the-icons--cache-limit. My suggestion (it is what I use in Helm) is to use the type, the file extension, a mode or a default if none found as key for the cache. This prevent increasing cache size dramatically. For this using a global var as cache and letting each function adding to cache seems the right thing to do IMHO.

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you were mentioned.*Message ID: @.***>

-- Thierry

wyuenho commented 4 weeks ago

Just coming back to this issue now. I think this issue reflects a fundamental problem with the design of all-the-icons, namely it really tries to provide all the icons lol.

I think as a further improvement to this experiment, it's probably best to break up all-the-icons into some-file-icons, some-mode-icons, some-ui-icons, and some-core-icons. Basically, like how VSCode does it.

Cache busting is still a concern tho, I may have to advice custom-push-theme and all those set-face-* functions. This is gonna be a pain.