piroor / treestyletab

Tree Style Tab, Show tabs like a tree.
http://piro.sakura.ne.jp/xul/treestyletab/
Other
3.46k stars 278 forks source link

High CPU usage for tab loading animation #1384

Closed SimonSapin closed 5 years ago

SimonSapin commented 6 years ago

Short description

The @keyframes throbber animation is expensive. "Normal" tabs in Firefox 57 / 58 have a similar-looking animation but it’s implemented in a completely different way:

As far as I understand, the latter is cheaper in current Firefox because things with animated transform are often painted into a separate layer that can be preserved and re-composited without re-painting when the transform changes. In comparison, changing the width of something with border-radius requires re-painting it.

Steps to reproduce

  1. Start Firefox with clean profile.
  2. Install TST.
  3. In a terminal, run netcat -l 8888 or some other command to start a TCP server that will accept an HTTP request but never send a response
  4. In Firefox, navigate to http://localhost:8888. This causes the tab to be "loading" until closed or stopped, and shows the "throbber" animation in place of the tab’s favicon.

Expected result

This animation by itself should not use a significant amount of resources.

Actual result

Firefox uses 40% ~ 80% CPU even though it is doing nothing else. This makes loading actual pages slower.

Environment

SimonSapin commented 6 years ago

Thank you for porting Tree Style Tabs to a WebExtension! I had not realized how much I missed it until installing it again :)

Thank you as well for including the "Extra style rules for sidebar contents" config, it’s a nice escape hatch for small hacks. I’ve used it to temporarily replace the throbber animation with something much cheaper (if uglier):

.tab.loading:not(.collapsed) .throbber::before,
:root.blocking-throbber #blocking-screen .throbber::before {
  animation: throbber 1s frames(2) infinite;
}

.throbber::before {
  box-shadow: none;
  border-radius: 0;
  border-width: 3px
}

@keyframes throbber {
  from { transform: translateX(-6px) }
}
piroor commented 6 years ago

There are two points:

After I read your descriptions, I thought that migration to transform seems the most important. Is it correct?

piroor commented 6 years ago

I experimentally rewrite the animation with CSS transforms. For more natural animation it should be improved more, but It actually reduces CPU usage for me. How about this?

@keyframes throbber {
  0% { transform: translateX(0); }
  3% { transform: translateX(0); }
  20% { transform: translateX(calc((var(--favicon-size) - var(--throbber-size)) / 2)) scaleX(2); }
  47% { transform: translateX(calc(var(--favicon-size) - var(--throbber-size))); }
  53% { transform: translateX(calc(var(--favicon-size) - var(--throbber-size))); }
  70% { transform: translateX(calc((var(--favicon-size) - var(--throbber-size)) / 2)) scaleX(2); }
  97% { transform: translateX(0); }
  100% { transform: translateX(0); }
}

.throbber::before {
  left: 0;
}

.tab.loading:not(.collapsed) .throbber::before,
:root.blocking-throbber #blocking-screen .throbber::before {
  animation: throbber 1.05s steps(60) infinite;
}
piroor commented 6 years ago

It still uses CPU 10% or more for me. Hmm...

asamuzaK commented 6 years ago

FYI this is how I animate the throbber. sidebarTabs/loading.svg

piroor commented 6 years ago

Finally I've replace the implementation totally. d56d1e6570eca9ef03f418c9378784cfe04971b0

asamuzaK commented 6 years ago

Your implementing throbber with ::before pseudo, right? Then I think you should check with RTL too (change intl.uidirection pref in about:config to 1). See 1392622#c19

piroor commented 6 years ago

I forgot to set svg.context-properties.content.enabled to true by myself... With default configuration of Firefox, SVG throbber become invisible on dark-colored themes. I've reverted changes for now.

piroor commented 6 years ago

@asamuzaK Thank you for advises! Updated version based on your code:

@keyframes throbber {
  0%    { transform: translateX(0) scale(1, 1); }
  17.5% { transform: translateX(calc((var(--favicon-size) - var(--throbber-size)) * 0.625)) scale(1.6, 0.9); }
  50%   { transform: translateX(calc(var(--favicon-size) - var(--throbber-size))) scale(1,1); }
  67.5% { transform: translateX(calc((var(--favicon-size) - var(--throbber-size)) * 0.625)) scale(1.6, 0.9); }
  100%  { transform: translateX(0) scale(1, 1); }
}

.throbber::before {
  left: 0;
}

.tab.loading:not(.collapsed) .throbber::before,
:root.blocking-throbber #blocking-screen .throbber::before {
  animation: throbber 1.05s ease infinite;
}
SimonSapin commented 6 years ago

The throbber is still using significant CPU on 2.0.1, at least for me. I suppose that using scale() still forces it to be re-painted (compared to Firefox’s throbber which only uses translateX() in steps within a sprite).

asamuzaK commented 6 years ago

Hmm, are there any noticeable differences in these three examples? test

nh2 commented 6 years ago

I'm using 2.0.7 and the loading symbol makes 90% CPU usage on my laptop.

Which of the above code snippets are still up-to-date that you'd recommend to mitigate this locally in the short term, no matter how ugly?

nh2 commented 6 years ago

Because, oddly even the solution from above (https://github.com/piroor/treestyletab/issues/1384#issuecomment-332620538) that just makes a square hop left and right wit 2 FPS, causes my Firefox to go > 50% CPU when connecting to http://localhost:1234 after running nc -l 1234 -k (so that I can observe the animation for forever).

This is assuming it is actually the animation that causes it and not something else about loading pages that hang in Firefox.

nh2 commented 6 years ago

This is assuming it is actually the animation that causes it and not something else about loading pages that hang in Firefox.

OK, turns out that this problem even exists with display: none:

.tab.loading:not(.collapsed) .throbber::before,
:root.blocking-throbber #blocking-screen .throbber::before {
  display: none;
}

Not sure if this is a TST or Firefox problem.

nh2 commented 6 years ago

Not sure if this is a TST or Firefox problem.

OK, so with TST disabled, the CPU usage goes down to 30%, still horrible for doing nothing.

strace shows that it's throwing out poll() syscalls like crazy. After closing the forever-loading tab still some polls show up but not at high frequency "busy polling" any more.

I wonder if what I'm observing is the CPU time taken by Firefox's actual loading animation even though I have hidden the top tab bar.

Lej77 commented 6 years ago

I experimented with Firefox's tab throbber animation a couple of weeks ago and from what I can remember it causes high CPU usage if it is hidden in any way. For example, it will causes high CPU usage if a throbber is placed outside of the visible area, if a throbber's opacity is set to 0 or if a throbber is set to display: none;. The only exception I found where the trobber could be invisible but still not use much CPU was if the throbber was inside the visible area but behind another element that covered it up.

Don't know if this happens anywhere in the extension but thought I should mention it anyways.

nh2 commented 6 years ago

Here's some more data, with a Thinkpad X220 which can report accurate power consumption information via the tp_smapi module (numbers in milliwatts; native FF tab bar is hidden, TST is shown):

while true; do cat /sys/devices/platform/smapi/BAT0/power_now; sleep 1; done
-13540
-13540
-12829
-12829
-11857
-11857
-12748
-12748
-12847
-12847
-12369
-12369
-24232 # throbber turned on here
-24232
-17086
-17086
-14579
-14579
-23677
-23677
-12429
-12429
-24202
-24202
-12623
-12623
-12600
-12600
-23664
-23664
-12047
-12047
-23512
-23512 # throbber turned off with ESC here
-11501
-11501
-11570
-11570
-16759
-16759
-16759
-11517
-11517
-14298
-14298
-11658
-11658
-11515
-11515

So the throbber doubles my laptop's power consumption, despite nothing being loaded.

(Using the blocky-jumpy throbber instead of the smooth one redures the problem only insignificantly, by ~1W.)

nh2 commented 6 years ago

it causes high CPU usage if it is hidden in any way

I can confirm what @Lej77 said. When NOT hiding the normal FF tab bar, then having a normal throbber in TST and the FF tab bar has this Wattage impact:

-11794
-11794
-11794
-11485
-11485
-11917
-11917
-12302 # normal throbber started here
-12302
-12305
-12305
-12876
-12876
-12260
-12260
-12952
-12952
-12470
-12470
-12832
-12832
-12405 # ESC pressed here (throbber stopped)
-12405
-11599
-11599
-11263
-11263
-11273
-11273
-11398

When hiding it with #TabsToolbar { visibility: collapse; }, I get this instead:

-11886
-11886
-11505
-11505
-11861
-11861
-11308
-11308
-11572
-11572
-24570 # Normal hrobber started here (hidden in FF tab bar, shown in TST)
-24570
-20856
-20856
-12579
-12579
-23575
-23575
-13146
-13146
-13146
-23857
-23857
-13464
-13464
-12597
-12597
-24094
-24094
-12931
-12931
-23418
-23418 # Pressed ESC to end throbber here
-13096
-13096
-16862
-16862
-11809
-11809
-11654
-11654
-11643
-11643
-11690
-11690

The alternating ~12W/24W are consistent with the alternating 20%/100% CPU usage shown in htop.

When the native FF tab bar is shown, then the CPU usage does not go over 15%.

Summary:

Lej77 commented 6 years ago

@nh2 What about when hiding FF tab bar and not using the TST sidebar?

nh2 commented 6 years ago

What about when hiding FF tab bar and not using the TST sidebar?

Summary:

Native bar on Native bar off
TST on + shown good slightly worse
TST disabled good HORRID

Edit: It also makes no difference if TST is entirely disabled or just the TST bar hidden with F1.

(As a reminder, to make a page that can't load I use nc as shown above.)

Lej77 commented 6 years ago

@nh2 So this is a bug in Firefox then? Its not an actual problem with TST itself?

Lej77 commented 6 years ago

@nh2 Also i wrote some more about hiding throbbers without performance issues on Issue 1543.

Don't know if that can be used to hide FF tab throbbers without performance issues though.

nh2 commented 6 years ago

I edited to add a bit more info:

It also makes no difference if TST is entirely disabled or just the TST bar hidden with F1.

@Lej77

So this is a bug in Firefox then? Its not an actual problem with TST itself?

I think there is at least a bug in Firefox, as even if TST is not installed, CPU usage goes up when the native tab bar is hidden.

For the HORRID entry in the table, it is unclear to me whether this is a bug in Firefox or in TST.

Lej77 commented 6 years ago

@nh2 How big is the difference between "good", "slightly worse" and "HORRID"?

nh2 commented 6 years ago

How big is the difference between "good", "slightly worse" and "HORRID"?

@Lej77 that's the numbers I reported above; in table form:

Native bar on Native bar off
TST on + shown < 15% CPU 30% CPU
TST disabled or hidden < 15% CPU 100% CPU
Lej77 commented 6 years ago

@nh2 Could you try with Tab Center Redux? It also has a tab throbber and it would be interesting if it causes different performance since it seems that extensions can affect the performance impact of hiding FF tab bar.

nh2 commented 6 years ago

Could you try with Tab Center Redux

@Lej77

With TabCenterRedux and the native bar on, everything is like with TST (< 15% CPU).

With the native bar off, the TabCenterRedux throbber also has a negative effect, but different:

WIth TCR, wattage is stable at around 15.7 W, it doesn't fluctuate between 12 and 24, and the CPU usage is also stable at around 50%.

So it seems overall TST and TCR are equally bad, just that TST alternates between low and very high CPU usage, and TCR, remains stable at the average between the low and high point.

Lej77 commented 6 years ago

On windows you can get infinite loading tabs by:

  1. Downloading Iperf (wikipedia) (download)
  2. Run the iperf3.exe file with the following command: iperf3.exe -s -p 8888
  3. Open tab with the link http://localhost:8888/
Lej77 commented 6 years ago

@nh2 I can't reproduce your high CPU usage for just one infinite loading tab. I get around 3-4% CPU with the sidebar open and 2-3% CPU with it closed. I am using TST v2.2.11 on Firefox 57 on Windows 10. Also firefox tab bar is hidden.

nh2 commented 6 years ago

@Lej77 I'm on Ubuntu 16.04, Firefox Nightly 59, TST v2.2.11.

Lej77 commented 6 years ago

@nh2 Using Windows 10 I have tested with Firefox 57 and Firefox Nightly 59. Both with and without hidden firefox tab bar. I do notice lower CPU usage for both 57 and 59 without hiding firefox tab bar but its only about 1% difference and the total CPU usage is about 2-6%.

Lej77 commented 6 years ago

@nh2 Also I noticed another bug when I was investigating this one. I wrote about it on a comment for Issue 1535. Don't know if you are affected but if you are then you might want to be careful not to trigger it. It should only be an issue if you have many tabs though.

SimonSapin commented 6 years ago

(As a reminder, to make a page that can't load I use nc as shown above.)

By the way, https://httpbin.org/ also has https://httpbin.org/delay/60 (where 60 is a number of seconds).

ssokolow commented 6 years ago

Depending on the state you're trying to get the browser into, you might also find the testcase I wrote for Bug 1276100 useful.

It's the only attachment and it's a Python script which holds an HTTP response incomplete at just the right stage to wedge the browser into a confusing in-between state. (Basically, Firefox malfunctions if the server leaves the response in a "document begun streaming but not enough received for an initial paint" state for long enough for the user to poke at it. There are details in the bug and the script itself will also present instructions when launched.)

bitonic commented 6 years ago

i have created https://bugzilla.mozilla.org/show_bug.cgi?id=1419096 to report this problem upstream, especially the fact that CPU usage increases if the throbber is hidden.

bitonic commented 6 years ago

also, by disabling firefox's own throbber in userChrome.css and the one in TST i'm able to keep the CPU (and thus the battery) under check. whad would be the simplest way to replace TST's throbber with just an icon?

wlonkly commented 6 years ago

@bitonic Mind sharing how you disabled both in userChrome.css to hold us over for now?

bitonic commented 6 years ago

ah yes, apologies:

/* disable throbber in the hope for better performance */

.tab-throbber[busy] {
  display: none;
}

.tab-throbber[progress] {
  display: none;
}

@wlonkly

msujaws commented 6 years ago

Can you please gather a performance profile? You can use the tool at https://perf-html.io/ to find which parts of the code are taking the most time.

untoreh commented 6 years ago

maybe this is related to tab cycling slowdowns when using extension hotkeys? switching between tabs with vim-vixen shift+j/k there is some serious lag compared to the master hotkeys ctrl+pgup/pgdown. Although I have tried completely disabling animations but it does not help

lightweight commented 6 years ago

I'm seeing the same problems with TST (and horizontal tab bar and top-of-TST bar successfully hidden via userChrome.css) on Linux Mint 18.3. @bitonic's CSS to hide the throbber isn't working for me... Still seeing the throbber everywhere, and massive CPU hits (with 60ish tabs open in 2 windows), including lots of blocking freezes (fairly frequently forcing a full kill -9 of Firefox from a console), especially on JS-intensive pages (e.g. tweetdeck).

hiikezoe commented 6 years ago

Hello @piroor! I noticed that the throbber animation does not specify 0% keyframe value.

From throbber.css; @keyframes throbber { 100% { transform: translateX(-100%); } }

If the animating element is scrolled out, the animation consumes more CPU than the situation where the animation is visible. I've been trying to fix it in bug 1419079, but if you could specify 0% value there (actually the value should not be transform:none, since we can't optimize the transform:none case), this high CPU usage will be solved. Thanks!

piroor commented 6 years ago

@hiikezoe Thanks a lot! By d651bdf I've added the 0% frame for the animation. Does it work as expected?

hiikezoe commented 6 years ago

@piroor Thanks for the quick fix! Yeah, that's exactly what I've expected. To be precise, bug 1190721 is also necessary to reduce the CPU usage, so we still need to wait for firefox 58.

piroor commented 6 years ago

@hiikezoe Now the animation target is intentionally placed out of screen and referrenced as a mask image via mask-image: -moz-element(...). Will bug 1190721 break this method?

hiikezoe commented 6 years ago

@piroor Interesting. Bug 1190721 just tries to stop calculating animation styles on out-of-view elements. I am not sure such animations referenced via -moz-element work fine. Would you mind uploading a simple test case somewhere? I'd like to see what happens there.

piroor commented 6 years ago

@hiikezoe I've created a simple testcase.

throbber.zip

It seems to work as expected on my environment: Nightly 59.0a1 (Build ID 20171127220446) on Ubuntu 16.04LTS.

hiikezoe commented 6 years ago

@piroor Thanks for the test case! The test case noticed me that there are two issues we need to tackle.

1) We don't yet optimize animations on out-of-view position:absolute-ed elements (bug 1421507) 2) We do accidentally optimize animations referenced by -moz-element (bug 1421506)

The throbber animation in the test case works fine due to 1) for now. Anyway bug 1190721 does not affect the test case at all, and I am going to do optimizations for animations with the test case in my mind.

I am not sure using a single animation and referencing the animating element from multiple elements is better for CPU usage rather than specifying an animation on each element respectively though.

SimonSapin commented 6 years ago

Does this off-screen throbber still exist when there are no visible throbber? Could https://bugzilla.mozilla.org/show_bug.cgi?id=1417460 be related to this?

piroor commented 6 years ago

@SimonSapin The throbber element exists always. But animation is applied only when there is one or more loading tab.

:root.have-loading-tab #master-throbber,
:root.blocking-throbber #master-throbber {
  animation: throbber 1.05s steps(60) infinite;
}

The class have-loading-tab appears only when there is any loading tab.