Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CPU usage even when idle (due to cursor rendering) #22900

Closed
joliss opened this issue Mar 20, 2017 · 64 comments
Closed

CPU usage even when idle (due to cursor rendering) #22900

joliss opened this issue Mar 20, 2017 · 64 comments
Assignees
Labels
bug Issue identified by VS Code Team member as probable bug editor-core Editor basic functionality perf
Milestone

Comments

@joliss
Copy link

joliss commented Mar 20, 2017

  • VSCode Version: 1.10.2 (8076a19)
  • OS Version: macOS Sierra 10.12.3

VS Code uses 13% CPU when focused and idle, draining battery. This is likely due to the blinking cursor rendering. I think CPU usage when focused-and-idle could ideally be near-0%.

To reproduce (this works with an empty settings file and all plugins disabled):

  1. Close all VS Code windows.
  2. Open a new window (File -> New Window). It will show the Welcome page.
  3. Open a new tab with an empty untitled file (File -> New Tab). The cursor is blinking.
  4. You should see VS Code consuming a non-negligible amount of CPU -- 13% on my 13" MacBook Pro.
  5. Cmd+Tab into some other application. Now the cursor isn't visible anymore.
  6. You should see VS Code consuming virtually no CPU.

I recorded a Timeline in the Developer Tools, and a cursory look suggests that the CPU activity comes from rendering the blinking cursor every 500ms.

Other macOS applications like Chrome or TextEdit show a blinking cursor without consuming much CPU, so I think this could surely be optimized away.

@joliss
Copy link
Author

joliss commented Mar 20, 2017

A workaround for folks who are similarly obsessed about battery life: Disabling cursor blinking will make CPU usage drop to 0. Here's the setting:

  "editor.cursorBlinking": "solid"

@joliss
Copy link
Author

joliss commented Mar 21, 2017

Some people on Twitter said they couldn't reproduce this, so I went and recorded a timeline in the Developer Tools to help debug.

TimelineRawData-20170321T114212.json.zip

  • Screenshot of the timeline:

    boot mz0y1
  • Zooming into a single frame, we see that while we render only 2 fps, the main thread is performing some work at 60 fps (every 16 ms) -- the thin lines marked with arrows:

    boot 684m3

  • Zooming all the way into one of those thin lines, we see that some rendering is happening at 60 fps:

    boot f9qau
  • When I take a CPU profile, it shows most CPU being spent in "(program)", not any specific JS function.

    boot g2wbo
  • When I set "editor.cursorBlinking": "solid", the problem mostly goes away: A periodic "Update Layer Tree" / "Paint" / "Composite Layers" is still happening, but only every 500ms, not every 16ms.

I hope this helps!

@rebornix
Copy link
Member

rebornix commented Mar 22, 2017

@joliss Quick answer to what's happening under the hood: a native animation updates at 60hz in Chromium.

Similar (almost the same) Chromium issue https://bugs.chromium.org/p/chromium/issues/detail?id=500259 and Chromium's tracking item https://bugs.chromium.org/p/chromium/issues/detail?id=361587

Our current CSS animation

@keyframes monaco-cursor-blink {
	50% {
		opacity: 0;
	}
	100% {
		opacity: 1;
	}
}

.cursor-blink {
	animation: monaco-cursor-blink 1s step-start 0s infinite;
}

Update

Quoting Paul Irish (Chrome guy) https://news.ycombinator.com/item?id=13941293

Powerful* text editors built on the web stack cannot rely on the OS text caret and have to provide their own.
In this case, VSCode is probably using the most reasonable approach to blinking a cursor: a step timing-function with a CSS keyframe animation. This tells the browser to only change the opacity every 500ms. Meanwhile, Chrome hasn't yet optimised this completely yet, hence http://crbug.com/361587.
So currently, Chrome is doing the full rendering lifecycle (style, paint, layers) every 16ms when it should be only doing that work at a 500ms interval. I'm confident that the engineers working on Chrome's style components can sort this out, but it'll take a little bit of work. I think the added visibility on this topic will likely escalate the priority of the fix. :)

  • Simple text editors, and basic ones built on [contenteditable] can, but those rarely scale to the feature set most want.

@rebornix rebornix added the editor-core Editor basic functionality label Mar 22, 2017
@graymalkin
Copy link

Will this change about background CPU usage of chromium tabs affect the editor?

@bpasero
Copy link
Member

bpasero commented Mar 23, 2017

Hopefully not (see electron/electron#7553). We do set backgroundThrottling to false since a while already.

@alexdima
Copy link
Member

Thank you @joliss @rebornix for the nice analysis.

Looks like we should go back to using setInterval on OSX.

p.s. Here's the initial cursor blinking logic :)
image

@bcherny
Copy link

bcherny commented Mar 23, 2017

@rebornix Here's a similar issue we ran into a while ago - https://bugs.chromium.org/p/chromium/issues/detail?id=658894. The workaround in our case was to remove the animating element from the DOM when it was not used, rather than just occluding it.

@alexdima alexdima added the bug Issue identified by VS Code Team member as probable bug label Mar 23, 2017
@alexdima alexdima added this to the March 2017 milestone Mar 23, 2017
@alexdima alexdima added the perf label Mar 23, 2017
@sunnykgupta
Copy link

@sunnykgupta
Copy link

There is also a css style for the same, not sure if it is used here though -

@keyframes monaco-cursor-blink {
	50% {
		opacity: 0;
	}
	100% {
		opacity: 1;
	}
}

https://github.com/Microsoft/vscode/blob/master/src/vs/editor/browser/viewParts/viewCursors/viewCursors.css

@camwest
Copy link

camwest commented Mar 23, 2017

Why not use https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback for the cursor animation?

@jlukic
Copy link

jlukic commented Mar 23, 2017

Use pageVisibility API's which let you know when page is hidden to disable the animation.

function handleVisibilityChange() {
  if (document.hidden) {
    // disable cursor animation with class name
  } 
  else  {
    // add back cursor animation
  }
}
document.addEventListener("visibilitychange", handleVisibilityChange, false);

@mehas
Copy link

mehas commented Mar 23, 2017

Suggestions here to render the CSS animation using the GPU instead of the CPU:

.cursor-blink {
	will-change: opacity;
}

@rebornix
Copy link
Member

rebornix commented Mar 23, 2017

In current implementation, if the editor loses focus, we'll remove the animation so life is easy. However if the editor has focus, it means the editor is visible (not hidden or in background) and active, users are working on it (reading is a good case) but don't trigger any view/content change. In this case we still need to display the blinking cursor even though it's kind of idle, that's what a blinking cursor is.

Initially this feature is implemented in JavaScript and about one year ago, we switched to CSS animation. Like @alexandrudima mentioned, we may want to move back to JS on OSX.

@camwest that api is charming, the only catch is compatibility (sadly Safari).

@mehas will-change is promising. I gave it a try, but Chromium doesn't optimize in this case. If you turn on Paint Flashing option in Dev Tools, you can see that this blinking cursor is not being repainted at all.

@jlukic @bcherny thanks for your suggestions. The thing we want to optimize is when the cursor is visible, active and blinking, so we still have this issue even though we have visibility check. But you are right, we should check visibility. Right now if you scroll the window to make the blinking cursor invisible, we don't have any optimization.

@joelday
Copy link
Contributor

joelday commented Mar 23, 2017

To be fair, Monaco is really, really, really complex. :)

@mrkite
Copy link

mrkite commented Mar 23, 2017

@rebornix will-change worked for my project, cpu usage disappeared entirely. However, my keyframes don't change opacity.. instead they change the element's color from 'inherit' to 'transparent'.

@eteeselink
Copy link

I'm just a lurker, and sorry if this is considered swearing, but wouldn't a two-frame animated gif do the trick? i'm not entirely sure how to test this conclusively, but I could imagine that even KHTML already had optimized paths for that, long before it transgromorphed into Chrome.

@matthiasg
Copy link

@eteeselink would be interesting to see, but cursor color needs to change and zoom etc. might not be trivial, switching back to js in the meantime might be easier.

@eteeselink
Copy link

eteeselink commented Mar 23, 2017

@matthiasg Ahyes, forgot about zoom.

Otherwise, autogenerating a blinking GIF based on the theme color shouldn't be too hard. It's probably a matter of patching a few bytes in a pre-baked file because while GIF pixel data is LZW-encoded, the palette data is uncompressed. But I bet zooming a gif like that makes it blurry, so maybe this is bad approach nonetheless.

If someone can come up with a way to make zooming a gif caret not suck, then I promise to contribute code that takes a color and produces a blinking-caret animated gif data url.

@Ultrabenosaurus
Copy link

@eteeselink you should absolutely gist that in vanilla JS anyway, maybe PHP and a few other flavours as well. I can imagine it would get several tonnes of usage all over the web, and then you'll be super-famous if you used a license that requires acknowledging the original author.

@julianlam
Copy link

@eteeselink If we're going around suggesting off-the-wall solutions, why don't we wrap the carat in <blink></blink> instead? 😉

rebornix added a commit to rebornix/vscode that referenced this issue Mar 23, 2017
@joshfriend
Copy link

joshfriend commented Mar 23, 2017

Please refrain from joining the Reddit brigade in making useless comments on issues and PRs where people are trying to make real improvements.

@o11c
Copy link

o11c commented Mar 23, 2017

if the editor has focus, it means the editor is visible

This is a false assumption. It's trivial to raise a window over another without giving it focus. Two ways I know of:

  • the "always draw on top" WM hint
  • when a window is moved between virtual desktops (since the stacking order is global, if a window was foremost on the previous desktop, it might not be on the new one).

Both are very common on their own, though they don't necessarily always cause problems.

@rebornix
Copy link
Member

rebornix commented Mar 23, 2017

@o11c thanks, my assumption was too wild when writing. Yes focus doesn't necessary means visible, scrolling the window is one case (I mentioned this in the same paragraph :( ). I don't know if focus+visible is the most common case but all of them should be mitigated.

@Daniel15
Copy link

@mehas

Suggestions here to render the CSS animation using the GPU instead of the CPU:

That just hides the issue by moving the load from the CPU to the GPU. It doesn't actually fix it.

@mehas
Copy link

mehas commented Mar 24, 2017

@Daniel15

That just hides the issue by moving the load from the CPU to the GPU. It doesn't actually fix it.

The load on the CPU is the problem, optimizing the animation to use the GPU does fix this Issue.

(That is separate from the question of the specific fix I suggested being the best one)

@ken-experiment-zz
Copy link

Not really sure how to reproduce it. I'm using the vim emulation plugin. Not really sure whether it's related.

I would like to know how this goes.

@Daniel15
Copy link

optimizing the animation to use the GPU does fix this issue.

It'll result in unnecessary GPU load though. That's a similar issue, except the load is on the graphics card's processor. It's not really a fix 😛

@kNmnPyGGCwxk4zQoDD
Copy link

Jesus was born in Africa.

You're welcome.

@efroim102
Copy link

Why not use a gif for the cursor?

@mehas
Copy link

mehas commented Mar 26, 2017

It'll result in unnecessary GPU load though. That's a similar issue

Right, but it's not this Issue, which is CPU usage even when idle.

@efroim102 #22900 (comment)

@MikeTheCanuck
Copy link

Oh dear gods, this debate will go in circles until it is decided what is the goal:

  1. Reduce battery consumption
  2. Reduce CPU contention

Assume (1) for the moment. Then we can be a little more data-driven about whether offloading from CPU to GPU with a workload such as this (a) uses the same amount of energy, (b) uses less energy, or (c) uses more energy. I have no expertise in these matters so I cannot predict which is true.

However, let's assume that GPU offload leads to (b); while not perfect, the trade-off may be acceptable enough to pursue this path while/if Chromium address their underlying issue. However, if the VSCode team deem it necessary to fix this once and for all, then offload will be an unlikely choice, and pursuit of the long-term fix (even if it takes quite a while) is preferable. At least in my insignificant opinion, it'll be good enough for me to know that attention is on this problem and that it will be prioritized when dependencies enable the intended fix.

(I'm sure I've confused some of the subtle details of what solutions are available/blocked, but hoping in writing this that folks can focus on asserting what problem they're hoping to solve rather than leaping into solution space.)

@ZombieProtectionAgency
Copy link

Wouldn't the GPU be better equipped to deal with a rendering load either way? That is exactly what it was made for.

@mehas
Copy link

mehas commented Mar 27, 2017

Agreed, we prioritize what uses the CPU and GPU differently, having a graphics-related feature use up graphics-specific resources makes more sense. A gain of CPU is not a 1:1 loss of GPU, and vice-versa. Mostly moot at this point (until the bug in Chromium is patched) but somewhat relevant as current solution still employs the CPU (albeit with much less weight).

@bit2shift
Copy link

I think people should stop using web technology on the desktop, as it requires shipping a fully-fledged browser with each program.
Not to mention that, if you have several of said programs, you have the same binaries duplicated all over the place.

We need to go back to bare metal.

PS: JScript should've been ditched 10 years ago, just like Obj-C should've been, had Apple not resurrected it.
PPS: ^^^^ this is a rant.

@eteeselink
Copy link

eteeselink commented Mar 29, 2017

PPS: ^^^^ this is a rant.

@bit2shift Indeed it is, and as such it doesn't belong in the comment thread about a very specific bug. Please don't use GitHub issue comments for rants.

@Daniel15
Copy link

@bit2shift - While I agree with some parts (like the fact that a browser-based app probably won't feel quite "native" compared to a true native app), how low-level is "bare metal" though? Machine code? Assembly language? C? C++? C#? There's always going to be several layers of abstraction, regardless of your technology stack.

Not to mention that, if you have several of said programs, you have the same binaries duplicated all over the place.

It's the same with any C# app that uses NuGet packages though. All the assembles are local to the app. .NET Core even distributes large parts of the framework as NuGet packages.

as it requires shipping a fully-fledged browser with each program.

Technically you could use the Edge engine, which would avoid needing to install a separate browser runtime. Internet Explorer did something similar nearly 20 years ago with HTML Applications. Another example is React Native, which uses the OS' native JS engine where available. For JavaScript-based apps, something like React Native for Windows is probably the future rather than web technologies, since it feels more native as it uses native UI widgets.

@bit2shift
Copy link

@Daniel15

(...) how low-level is "bare metal" though? Machine code? Assembly language? C? C++? C#?

Any language that is compilable to machine code (only source -> ELF/PE counts) is considered "bare metal" in this context, so layers of abstraction don't matter here.

Internet Explorer did something similar nearly 20 years ago with HTML Applications.

"something similar" as in having mshta.exe use mshtml.dll that resides in system32 since the '90s.

Another example is React Native, which uses the OS' native JS engine where available. For JavaScript-based apps, something like React Native for Windows is probably the future rather than web technologies, since it feels more native as it uses native UI widgets.

If high performance is desired in desktop JS apps, then someone with enough time in their hands should write a frontend for LLVM.
You'd lose the ability to run arbitrary snippets of code, but that would be an advantage from a security standpoint.

/thread

@mgol
Copy link

mgol commented Mar 29, 2017

Can you not derail this thread, please? This is an issue about a specific performance issue with a blinking cursor and it's definitely not a place for discussing general merits of developing JS-based desktop apps.

You're just spamming mailboxes of people that are not interested in these rants.

@sarim
Copy link

sarim commented Mar 31, 2017

How about putting a <input type=text> with 2px * 10px size in place where the cursor it. That way it'll use the native blinking cursor of the <input> :P <blink></blink> 👍

Also i'm not sure how severe this issue is, when VSCode is idle (i'm not writing code) it is almost always not in focus, focus is on other application i'm in. And cursor stops blinking.

@bboydflo
Copy link

from my experience, gifs are not so good. I once had a loading spinner as a gif image. and used it while interacting with indexeddb. sometimes I could see the spinner stop, even on the desktop.

@Tyriar
Copy link
Member

Tyriar commented Apr 4, 2017

Pushed the terminal cursor change to master and release/1.11 (CSS animation -> setInterval).

@rebornix
Copy link
Member

rebornix commented Apr 4, 2017

Thank you all for looking into this issue. Your investigations and suggestions helped us find the root cause and possible solutions! We compared JavaScript setInterval, animated gif and several other techniques, and we settled on the following solution:

  • If editor.cursorBlinking is set to blink or terminal.integrated.cursorBlinking is set to true, the blinking logic is now implemented in JavaScript. Our testing shows the CPU usage drops to less than 1%.
  • If editor.cursorBlinking is set to smooth, expand or phase, which use CSS easing functions, we'll stop the blinking animation after the cursor is idle for 10 seconds.

This fix is already in our Insider build and it will be available in our April Stable build which will be released in the next few days. It would be great if some of you could verify our test results. If you see problems, please create a new issue.

@rebornix rebornix closed this as completed Apr 4, 2017
@jednano
Copy link

jednano commented Apr 4, 2017

@rebornix did you mean to say "or" terminal.integrated.cursorBlinking is set to true? Or was the "and" intentional?

@rebornix
Copy link
Member

rebornix commented Apr 5, 2017

@jedmao thanks for the correction, it should or.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
bug Issue identified by VS Code Team member as probable bug editor-core Editor basic functionality perf
Projects
None yet
Development

No branches or pull requests