Loading lots of icons is slow #124

Closed
opened 4 months ago by dnkl · 11 comments
dnkl commented 4 months ago
Owner

Currently, icons are loaded in the background, and the UI is updated once all icons have been loaded.

This can be a fairly slow operation, if there are many desktop entries to go through.

We should try to improve the perceived performance here. That is, somehow get the visible entries' icons to show up quicker.

Note that I don't think it's rendering of the icons that is slow, but looking them up, as that involves a lot of disk I/O.

We should:

  • measure how long time it takes to load the icons
  • measure how long time it takes to render a full page of entries with SVG icons.

Start with whatever takes most time, and try to improve performance.

Currently, icons are loaded in the background, and the UI is updated once **all** icons have been loaded. This can be a fairly slow operation, if there are many desktop entries to go through. We should try to improve the perceived performance here. That is, somehow get the **visible entries'** icons to show up quicker. Note that I don't _think_ it's **rendering** of the icons that is slow, but looking them up, as that involves a lot of disk I/O. We should: * measure how long time it takes to **load** the icons * measure how long time it takes to **render** a full page of entries with SVG icons. Start with whatever takes most time, and try to improve performance.
Poster
Owner

For me, loading the icons takes roughly the same time as rendering. It's at least of the same magnitute.

info: wayland.c:1713: eDP-1: 1920x1080+0x0@120Hz 0x42ED 15.33" scale=1 PPI=147x154 (physical) PPI=147x154 (logical), DPI=143.66
info: fcft.c:314: fcft: 3.0.1-9-gb0d44ee (Apr 14 2022, branch 'master') +graphemes +runs -assertions
info: fcft.c:353: fontconfig: 2.14.0, freetype: 2.12.0, harfbuzz: 4.2.0, utf8proc: 2.7.0 (Unicode 14.0.0)
info: fcft.c:815: /usr/share/fonts/misc/DinaMedium10.pcf.gz: size=12.00pt/13px, dpi=143.66
warn: icon.c:573: reloaded icons in 0s 000000119ns
info: fcft.c:815: /usr/share/fonts/TTF/DejaVuSansMono.ttf: size=12.00pt/24px, dpi=143.66
warn: render.c:672: rendered in 0s 000000287ns
info: main.c:397: theme: candy-icons
warn: render.c:672: rendered in 0s 011021415ns
warn: icon.c:573: reloaded icons in 0s 082040980ns
warn: render.c:672: rendered in 0s 053693747ns

It would be interresting to see what number people with lots of applications get, with the attached patch.

For me, loading the icons takes roughly the same time as rendering. It's at least of the same magnitute. ``` info: wayland.c:1713: eDP-1: 1920x1080+0x0@120Hz 0x42ED 15.33" scale=1 PPI=147x154 (physical) PPI=147x154 (logical), DPI=143.66 info: fcft.c:314: fcft: 3.0.1-9-gb0d44ee (Apr 14 2022, branch 'master') +graphemes +runs -assertions info: fcft.c:353: fontconfig: 2.14.0, freetype: 2.12.0, harfbuzz: 4.2.0, utf8proc: 2.7.0 (Unicode 14.0.0) info: fcft.c:815: /usr/share/fonts/misc/DinaMedium10.pcf.gz: size=12.00pt/13px, dpi=143.66 warn: icon.c:573: reloaded icons in 0s 000000119ns info: fcft.c:815: /usr/share/fonts/TTF/DejaVuSansMono.ttf: size=12.00pt/24px, dpi=143.66 warn: render.c:672: rendered in 0s 000000287ns info: main.c:397: theme: candy-icons warn: render.c:672: rendered in 0s 011021415ns warn: icon.c:573: reloaded icons in 0s 082040980ns warn: render.c:672: rendered in 0s 053693747ns ``` It would be interresting to see what number people with **lots** of applications get, with the attached patch.

Here's my output:

info: wayland.c:1709: DP-2: 3440x1440+1920x0@60Hz ROG PG348Q 34.22" scale=2 PPI=110x110 (physical) PPI=73x73 (logical), DPI=145.28
info: fcft.c:314: fcft: 3.0.1 +graphemes +runs -assertions
info: fcft.c:324: fontconfig: 2.14.0
info: fcft.c:330: freetype: 2.12.0
info: fcft.c:794: /usr/share/fonts/TTF/FiraCode-Regular.ttf: size=24.00pt/32px, dpi=96.00
warn: icon.c:573: reloaded icons in 0s 000000055ns
warn: render.c:672: rendered in 0s 000000106ns
warn: render.c:672: rendered in 0s 003505337ns
info: main.c:397: theme: hicolor
warn: icon.c:573: reloaded icons in 0s 268648175ns
warn: render.c:672: rendered in 0s 073269245ns

Doing some searches:

warn: render.c:672: rendered in 0s 046361863ns
warn: render.c:672: rendered in 0s 065951707ns
warn: render.c:672: rendered in 0s 000385573ns
warn: render.c:672: rendered in 0s 000301635ns
warn: render.c:672: rendered in 0s 000068648ns
warn: render.c:672: rendered in 0s 000044048ns
warn: render.c:672: rendered in 0s 000042502ns

I think parsing the SVGs is slowest. A script that parses them and puts pre-scaled version in /usr/share/icons/... might make sense, especially since it benefits any application showing icons. I can try and run something like this as a pacman hook and see it if makes a difference.

That aside, reading only the icons for entries that are displayed would be ideal, especially if they can be rendered onto the surface individually as they're loaded, and the damage track just that region. That might be pretty complex tho.

Here's my output: ``` info: wayland.c:1709: DP-2: 3440x1440+1920x0@60Hz ROG PG348Q 34.22" scale=2 PPI=110x110 (physical) PPI=73x73 (logical), DPI=145.28 info: fcft.c:314: fcft: 3.0.1 +graphemes +runs -assertions info: fcft.c:324: fontconfig: 2.14.0 info: fcft.c:330: freetype: 2.12.0 info: fcft.c:794: /usr/share/fonts/TTF/FiraCode-Regular.ttf: size=24.00pt/32px, dpi=96.00 warn: icon.c:573: reloaded icons in 0s 000000055ns warn: render.c:672: rendered in 0s 000000106ns warn: render.c:672: rendered in 0s 003505337ns info: main.c:397: theme: hicolor warn: icon.c:573: reloaded icons in 0s 268648175ns warn: render.c:672: rendered in 0s 073269245ns ``` Doing some searches: ``` warn: render.c:672: rendered in 0s 046361863ns warn: render.c:672: rendered in 0s 065951707ns warn: render.c:672: rendered in 0s 000385573ns warn: render.c:672: rendered in 0s 000301635ns warn: render.c:672: rendered in 0s 000068648ns warn: render.c:672: rendered in 0s 000044048ns warn: render.c:672: rendered in 0s 000042502ns ``` I think parsing the SVGs is slowest. A script that parses them and puts pre-scaled version in /usr/share/icons/... might make sense, especially since it benefits _any_ application showing icons. I can try and run something like this as a pacman hook and see it if makes a difference. That aside, reading only the icons for entries that are displayed would be ideal, especially if they can be rendered onto the surface individually as they're loaded, and the damage track just that region. That might be pretty complex tho.
Poster
Owner

I think parsing the SVGs is slowest

At least when there are many icons. Rendering is limited by the number of entries displayed.

I'm doing some work in this area right now. We'll see how far that takes us.

A script that parses them and puts pre-scaled version in /usr/share/icons/... might make sense, especially since it benefits any application showing icons.

It certainly would be nice if there were a standardized way of caching pre-rendered SVG icons...

Another option (than running a script) is to implement on-disk caching in fuzzel. Don't really want to go there though...

That aside, reading only the icons for entries that are displayed would be ideal, especially if they can be rendered onto the surface individually as they're loaded, and the damage track just that region. That might be pretty complex tho.

I'm going to see how far we can take things without restructuring the code (too much). But yeah, having a priority based queueu (putting the icons of the currently visible entries at the top), and refreshing the UI more often is probably where we'll end up.

> I think parsing the SVGs is slowest At least when there are many icons. Rendering is limited by the number of entries displayed. I'm doing some work in this area right now. We'll see how far that takes us. > A script that parses them and puts pre-scaled version in /usr/share/icons/... might make sense, especially since it benefits any application showing icons. It certainly would be nice if there were a standardized way of caching pre-rendered SVG icons... Another option (than running a script) is to implement on-disk caching in fuzzel. Don't really want to go there though... > That aside, reading only the icons for entries that are displayed would be ideal, especially if they can be rendered onto the surface individually as they're loaded, and the damage track just that region. That might be pretty complex tho. I'm going to see how far we can take things without restructuring the code (too much). But yeah, having a priority based queueu (putting the icons of the currently visible entries at the top), and refreshing the UI more often is probably where we'll end up.

Running stable release.

info: wayland.c:1709: HDMI-A-2: 1920x1080+0x0@60Hz DELL U2414H 23.98" scale=1 PPI=96x98 (physical) PPI=96x98 (logical), DPI=91.88
info: fcft.c:314: fcft: 3.0.1 +graphemes +runs -assertions
info: fcft.c:324: fontconfig: 2.14.0
info: fcft.c:330: freetype: 2.12.0
info: fcft.c:794: /usr/share/fonts/noto/NotoSansMono.ttf: size=12.00pt/15px, dpi=91.88
warn: icon.c:573: reloaded icons in 0s 000000067ns
warn: render.c:664: rendered in 0s 000000263ns
info: wayland.c:333: cursor theme: (null), size: 24, scale: 1
info: main.c:393: theme: hicolor
warn: icon.c:573: reloaded icons in 0s 557480146ns
warn: render.c:664: rendered in 0s 070561692ns
warn: render.c:664: rendered in 0s 010377777ns
warn: render.c:664: rendered in 0s 015332795ns
warn: render.c:664: rendered in 0s 000134190ns
warn: render.c:664: rendered in 0s 000054480ns
warn: render.c:664: rendered in 0s 003725724ns
warn: render.c:664: rendered in 0s 000035661ns
warn: render.c:664: rendered in 0s 000525444ns
warn: render.c:664: rendered in 0s 000223504ns
warn: render.c:664: rendered in 0s 000180219ns
warn: render.c:664: rendered in 0s 000164499ns
warn: render.c:664: rendered in 0s 000165764ns
warn: render.c:664: rendered in 0s 042971170ns
warn: render.c:664: rendered in 0s 061382938ns
warn: render.c:664: rendered in 0s 040742838ns
warn: render.c:664: rendered in 0s 000158723ns
warn: render.c:664: rendered in 0s 000143577ns
warn: render.c:664: rendered in 0s 000142476ns
warn: render.c:664: rendered in 0s 072028031ns
warn: render.c:664: rendered in 0s 092335692ns
warn: render.c:664: rendered in 0s 021031368ns
warn: render.c:664: rendered in 0s 000031089ns
warn: render.c:664: rendered in 0s 000026637ns
warn: render.c:664: rendered in 0s 000023669ns
warn: render.c:664: rendered in 0s 000201158ns
warn: render.c:664: rendered in 0s 000145169ns
warn: render.c:664: rendered in 0s 000356671ns
warn: render.c:664: rendered in 0s 000142713ns
warn: render.c:664: rendered in 0s 000143567ns
warn: render.c:664: rendered in 0s 000158569ns
warn: render.c:664: rendered in 0s 081090354ns
warn: render.c:664: rendered in 0s 015743248ns
warn: render.c:664: rendered in 0s 013011392ns
warn: render.c:664: rendered in 0s 000125381ns
warn: render.c:664: rendered in 0s 000120187ns
warn: render.c:664: rendered in 0s 000157600ns
warn: render.c:664: rendered in 0s 000138510ns
warn: render.c:664: rendered in 0s 000150441ns
warn: render.c:664: rendered in 0s 069680708ns
warn: render.c:664: rendered in 0s 012016036ns
warn: render.c:664: rendered in 0s 000470701ns
warn: render.c:664: rendered in 0s 000247845ns
warn: render.c:664: rendered in 0s 000134801ns
warn: render.c:664: rendered in 0s 000134298ns
warn: render.c:664: rendered in 0s 000135135ns
Running stable release. ``` info: wayland.c:1709: HDMI-A-2: 1920x1080+0x0@60Hz DELL U2414H 23.98" scale=1 PPI=96x98 (physical) PPI=96x98 (logical), DPI=91.88 info: fcft.c:314: fcft: 3.0.1 +graphemes +runs -assertions info: fcft.c:324: fontconfig: 2.14.0 info: fcft.c:330: freetype: 2.12.0 info: fcft.c:794: /usr/share/fonts/noto/NotoSansMono.ttf: size=12.00pt/15px, dpi=91.88 warn: icon.c:573: reloaded icons in 0s 000000067ns warn: render.c:664: rendered in 0s 000000263ns info: wayland.c:333: cursor theme: (null), size: 24, scale: 1 info: main.c:393: theme: hicolor warn: icon.c:573: reloaded icons in 0s 557480146ns warn: render.c:664: rendered in 0s 070561692ns warn: render.c:664: rendered in 0s 010377777ns warn: render.c:664: rendered in 0s 015332795ns warn: render.c:664: rendered in 0s 000134190ns warn: render.c:664: rendered in 0s 000054480ns warn: render.c:664: rendered in 0s 003725724ns warn: render.c:664: rendered in 0s 000035661ns warn: render.c:664: rendered in 0s 000525444ns warn: render.c:664: rendered in 0s 000223504ns warn: render.c:664: rendered in 0s 000180219ns warn: render.c:664: rendered in 0s 000164499ns warn: render.c:664: rendered in 0s 000165764ns warn: render.c:664: rendered in 0s 042971170ns warn: render.c:664: rendered in 0s 061382938ns warn: render.c:664: rendered in 0s 040742838ns warn: render.c:664: rendered in 0s 000158723ns warn: render.c:664: rendered in 0s 000143577ns warn: render.c:664: rendered in 0s 000142476ns warn: render.c:664: rendered in 0s 072028031ns warn: render.c:664: rendered in 0s 092335692ns warn: render.c:664: rendered in 0s 021031368ns warn: render.c:664: rendered in 0s 000031089ns warn: render.c:664: rendered in 0s 000026637ns warn: render.c:664: rendered in 0s 000023669ns warn: render.c:664: rendered in 0s 000201158ns warn: render.c:664: rendered in 0s 000145169ns warn: render.c:664: rendered in 0s 000356671ns warn: render.c:664: rendered in 0s 000142713ns warn: render.c:664: rendered in 0s 000143567ns warn: render.c:664: rendered in 0s 000158569ns warn: render.c:664: rendered in 0s 081090354ns warn: render.c:664: rendered in 0s 015743248ns warn: render.c:664: rendered in 0s 013011392ns warn: render.c:664: rendered in 0s 000125381ns warn: render.c:664: rendered in 0s 000120187ns warn: render.c:664: rendered in 0s 000157600ns warn: render.c:664: rendered in 0s 000138510ns warn: render.c:664: rendered in 0s 000150441ns warn: render.c:664: rendered in 0s 069680708ns warn: render.c:664: rendered in 0s 012016036ns warn: render.c:664: rendered in 0s 000470701ns warn: render.c:664: rendered in 0s 000247845ns warn: render.c:664: rendered in 0s 000134801ns warn: render.c:664: rendered in 0s 000134298ns warn: render.c:664: rendered in 0s 000135135ns ```

p.s.

$ find /usr/share/applications /apps/exports ~/.local/share/flatpak/exports -name '*.desktop'  -exec grep 'Icon=' '{}' \; | wc -l

318
p.s. ``` $ find /usr/share/applications /apps/exports ~/.local/share/flatpak/exports -name '*.desktop' -exec grep 'Icon=' '{}' \; | wc -l 318 ```
Poster
Owner

Ok, so looks like it's the loading logic we should start looking at.

It's pretty obvious from all three traces that the initial frame is slow to render as well. Subsequent frames are faster. This is because we're caching the rendered version in memory.

I'll be focusing on two things, initially. One, cut down on disk accesses; skip non-existing directories etc. And two, reduce the number of iterations done per icon, by better matching the lookup logic described in https://specifications.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html.

Ok, so looks like it's the **loading** logic we should start looking at. It's pretty obvious from all three traces that the **initial** frame is slow to **render** as well. Subsequent frames are faster. This is because we're caching the rendered version in memory. I'll be focusing on two things, initially. One, cut down on disk accesses; skip non-existing directories etc. And two, reduce the number of iterations done per icon, by better matching the lookup logic described in https://specifications.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html.
Poster
Owner

It'd also be interresting to compare both load- and rendering times with the nanosvg and the librsvg backends.

It'd also be interresting to compare both load- and rendering times with the nanosvg and the librsvg backends.

It's pretty obvious from all three traces that the initial frame is slow to render as well. Subsequent frames are faster. This is because we're caching the rendered version in memory.

Right, there's three distinct steps here:

  • Finding the icons. This requires all the directory traversal, etc.
  • Parsing the icon. This means reading the raw file and converting it into something that can be rendered on screen. E.g.: Converting SVG into bitmaps.
  • Rendering the icon. This is done each time it's visible on-screen, including when it shifts position due to the search query changing.

Each icon gets parsed only once, but may be rendered more than once. You're currently counting parse time as render time, hence why it seems "slower the first time".

It certainly would be nice if there were a standardized way of caching pre-rendered SVG icons...

The XDG specs allows for a single application to provide icons in different formats. For example, it's perfectly valid to have an SVG and a PNGs for a same application.

What I'm thinking of this script is basically:

  • Read all SVG icons in /usr/share/icons/
  • Render pre-scaled PNGs for the sizes fuzzel needs
  • Put these rendered files into the right place following the XDG spec.

I'd run this script as a hook on my package manager, to generate the scaled icons whenever something gets installed.

All fuzzel needs to do is prefer the PNG when the both options are available. FWIW, this script would also really speed up any other launchehr that has the same preference too.

> It's pretty obvious from all three traces that the initial frame is slow to render as well. Subsequent frames are faster. This is because we're caching the rendered version in memory. Right, there's three distinct steps here: - **Finding** the icons. This requires all the directory traversal, etc. - **Parsing** the icon. This means reading the raw file and converting it into something that can be rendered on screen. E.g.: Converting SVG into bitmaps. - **Rendering** the icon. This is done each time it's visible on-screen, including when it shifts position due to the search query changing. Each icon gets parsed only once, but may be rendered more than once. You're currently counting parse time as render time, hence why it seems "slower the first time". > It certainly would be nice if there were a standardized way of caching pre-rendered SVG icons... The XDG specs allows for a single application to provide icons in different formats. For example, it's perfectly valid to have an SVG _and_ a PNGs for a same application. What I'm thinking of this script is basically: - Read all SVG icons in /usr/share/icons/ - Render pre-scaled PNGs for the sizes fuzzel needs - Put these rendered files into the right place following the XDG spec. I'd run this script as a hook on my package manager, to generate the scaled icons whenever something gets installed. All fuzzel needs to do is prefer the PNG when the both options are available. FWIW, this script would also really speed up _any other_ launchehr that has the same preference too.
Poster
Owner

You're currently counting parse time as render time, hence why it seems "slower the first time".

No, parse time is included in load time. The reason the first frame is rendered slower, is because an SVG (and PNGs too, to some extent) needs to be, well, rendered. After the first frame, we have a cached pixman image, and subsequent frames just need to blit that.

What we could do, as an intermediate step (before going full priority queue), is defer parsing the icons. That is, lookup all icons, like now, but don't actually load (parse) them. Do this the first time they need to be rendered.

This would reduce the initial delay. But would also increase the time it takes to render the initial frame.

It's something that is relatively easy to test however, to see if it feels better.

All fuzzel needs to do is prefer the PNG when the both options are available. FWIW, this script would also really speed up any other launchehr that has the same preference too.

Each theme defines it's own set of "sub-directories", and fuzzel scans these in the order they are defined by the theme. In #125, fuzzel tries .svg first, if the directory's type is Scalable. Otherwise it tries .png first. I could switch back to looking for .png first, but if you want fuzzel to find the pre-rendered .png first, it needs to go into the theme's prioritized directory. Though, I see a bug in fuzzel here: we currently scan them in the order we found their sections in the theme's index.theme file, but should be scanning them in the order they are listed in the Directories option.

> You're currently counting parse time as render time, hence why it seems "slower the first time". No, parse time is included in load time. The reason the first frame is rendered slower, is because an SVG (and PNGs too, to some extent) needs to be, well, rendered. After the first frame, we have a cached pixman image, and subsequent frames just need to blit that. What we could do, as an intermediate step (before going full priority queue), is defer parsing the icons. That is, **lookup all** icons, like now, but don't actually load (parse) them. Do this the first time they need to be rendered. This would reduce the initial delay. But would also increase the time it takes to render the initial frame. It's something that is relatively easy to test however, to see if it feels better. > All fuzzel needs to do is prefer the PNG when the both options are available. FWIW, this script would also really speed up any other launchehr that has the same preference too. Each theme defines it's own set of "sub-directories", and fuzzel scans these in the order they are defined by the theme. In #125, fuzzel tries `.svg` first, **if** the directory's type is `Scalable`. Otherwise it tries `.png` first. I could switch back to looking for `.png` first, but if you want fuzzel to find the pre-rendered `.png` first, it needs to go into the theme's prioritized directory. Though, I see a bug in fuzzel here: we currently scan them in the order we found their sections in the theme's `index.theme` file, but should be scanning them in the order they are listed in the `Directories` option.
Poster
Owner

Though, I see a bug in fuzzel here: we currently scan them in the order we found their sections in the theme's index.theme file, but should be scanning them in the order they are listed in the Directories option.

Fixed in #125

> Though, I see a bug in fuzzel here: we currently scan them in the order we found their sections in the theme's index.theme file, but should be scanning them in the order they are listed in the Directories option. Fixed in #125
Poster
Owner

All fuzzel needs to do is prefer the PNG when the both options are available

PR has been updated/reverted to always check for PNGs first.

> All fuzzel needs to do is prefer the PNG when the both options are available PR has been updated/reverted to always check for PNGs first.
dnkl added the
performance
label 4 months ago
dnkl closed this issue 4 months ago
Sign in to join this conversation.
No Milestone
No Assignees
3 Participants
Notifications
Due Date

No due date set.

Dependencies

No dependencies set.

Reference: dnkl/fuzzel#124
Loading…
There is no content yet.