-
Notifications
You must be signed in to change notification settings - Fork 1.7k
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
Remove inner setTimeout(), prevents high CPU usage. #1447
Conversation
It appears that the recursive timeout was leaking memory and causing high CPU usage. Prior to this change running top (via xterm) showed the CPU usage for the WebKitProcess around 30-50%. Afterwards CPU usage is between 5-20% and rendering is much faster visually which is particularly useful for ncurses applications.
Note that some long running commands such as reader := func(ru rune, n int) (err error) {
// Pause to give rendering some time
time.Sleep(time.Microsecond)
buffer := []byte(string(ru))
return conn.WriteMessage(websocket.TextMessage, buffer)
}
p.Pipe(reader) Thanks for xterm, I now have it running on webkit2 embedded using golang to proxy to a pty and performance is not so far off my actual terminal now 👍 |
BTW, If I switch the outer |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@@ -1324,7 +1324,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II | |||
} | |||
if (this.writeBuffer.length > 0) { | |||
// Allow renderer to catch up before processing the next batch | |||
setTimeout(() => this._innerWrite(), 0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This timeout allows the renderer to catch up, without it there may not be a draw for some time. ie. the frame rate can drop significantly
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes this change basically reduces screen updates by 90%, now I can count the updates for my typical benchmark with ls -lR /usr/lib
. Overall runtime is 10% better with less CPU usage, saved by less time consumed in painting (the greenish pie part). JS runtime is the same (so not the fault of timeout
per se).
@@ -1290,7 +1290,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II | |||
// Kick off a write which will write all data in sequence recursively | |||
this._writeInProgress = true; | |||
// Kick off an async innerWrite so more writes can come in while processing data | |||
setTimeout(() => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could delay parsing by 1+ frames. We could maybe just remove this timeout instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This cant be removed as it is now, it gives the renderer time to do anything atm. (Removing it will completely disable updates while a command is running, which is reallly fast but prolly not wanted, lol).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the timeout that starts a batch, would considering a batch immediately be no good?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For my tests with ls -lR /usr/lib
and find /
the emulator went silent for a couple of seconds and updated the screen once in a while (like every 3s, for ls
it was black until the command had finished). I did not step debug this, seems the updates are way behind without the timeout here.
This PR tackles a tricky topic, that might need further work and benchmarks to get it right. It boils down to the following requirements:
@tmpfs Imho this is also a matter of favour - do we want pure performance (less screen updates) vs. reliable behavior (more screen updates as feedback to the user). Also if the performance differs alot between different setups - do we need a runtime benchmark to adjust it on the fly? Edit: |
Thanks for looking into this and the information. Clearly, I am only just starting to understand the xterm code and it is quite possible that the performance I was experiencing was very specific to my setup which I will describe in more detail. If it is the case that it is specific to my use case maybe we could add this behaviour via
My machine is fairly powerful (XPS 13 with 16GB RAM and 4 i7 cores):
Here is how my app works:
Note that I am launching the GUI window fullscreen to really give it a work out ;) I think the big difference is that I am not using websockets for writing to the pty to try to get as close to near-native performance as possible. Soon, I hope a friend will be compiling and running the app on OSX and then we can see the behaviour with webkit on OSX. Update: Sorry, just found out that the performance improvements I was seeing only apply when these changes are applied to the |
Ok, so I've been looking into this much deeper and found out some stuff, clearly only appears to be webkit specific. I think we need to test for export const isSafari = !!~userAgent.indexOf('Safari'); And then updated if (!('createImageBitmap' in context) || isFirefox || isSafari) { In combination with allowing setting If I put these less intrusive changes in a separate PR could you take a look please? |
When I optimize I tend to try to get the maximum frame rate/responsiveness while also trying to minimize the time it takes to respond to keystrokes (like It looks like @tmpfs is optimizing for CPU and run time which is kind of the opposite 😄, that's all about minimizing draws which is a big hit to perceived performance.
Problems with Safari just came up in #1441, I removed Firefox from this since it was much slower via
What approximate size seems to work best for you? I'd rather not expose such a low level setting as an option. |
@tmpfs @Tyriar |
@jerch, thanks so much for the pointer, that was indeed the problem! So my mistake was treating it as a character stream, I've switched the read logic to read a buffer (with some additional logic to validate the buffer as utf8 otherwise I get websocket utf8 decode errors) and it is working very fast using vanilla I am going to close this as not necessary, however there is one small issue related to the fix I mentioned above and that is I still see the promise rejections on
The crude Thanks everyone for your help resolving this issue. |
@tmpfs thanks for sparking the interesting discussion 😃
|
I would like to see these Safari bitmap issues resolved! Did we decide to ship an exception for Safari users, like Firefox? |
@vincentwoo I don't think there's been a PR yet for it but I'm all for adding it. |
It appears that the recursive timeout was leaking memory and causing
high CPU usage. Prior to this change running top (via xterm) showed the
CPU usage for the WebKitProcess around 30-50%. Afterwards CPU usage is
between 5-20% and rendering is much faster visually which is
particularly useful for ncurses applications.
All tests continue to pass.