Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.
/ corefx Public archive

Console: Unix: Improve Cursor{Left,Top} performance by caching them #36049

Merged
merged 9 commits into from
Mar 22, 2019

Conversation

tmds
Copy link
Member

@tmds tmds commented Mar 14, 2019

We keep a cached version for CursorLeft and CursorTop. When we write
data to stdout, we try to update the cached values based on the buffer
content, or we invalidate the cursor position.

A lock is used to deal with multi-threading issues. We synchronize
between some blocks using a 'version' field. This field is incremented
each time a cursor change may have occured. If the value has not
changed between two blocks, we know the the cursor is still at the same
position.

On signals that may mean the cursor position has changed (SIGCONT,
SIGCHLD, SIGWINCH) we invalidate the cached values.

Fixes https://github.com/dotnet/corefx/issues/32174
Fixes https://github.com/dotnet/corefx/issues/31517
Contributes to https://github.com/dotnet/corefx/issues/34885

We keep a cached version for CursorLeft and CursorTop. When we write
data to stdout, we try to update the cached values based on the buffer
content, or we invalidate the cursor position.

A lock is used to deal with multi-threading issues. We synchronize
between some blocks using a 'version' field. This field is incremented
each time a cursor change may have occured. If the value has not
changed between two blocks, we know the the cursor is still at the same
position.

On signals that may mean the cursor position has changed (SIGCONT,
SIGCHLD, SIGWINCH) we invalidate the cached values.
@tmds
Copy link
Member Author

tmds commented Mar 14, 2019

@stephentoub PTAL. It was a challenge to get to this PR.

Copy link
Member

@stephentoub stephentoub left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working on this! If we can land it and have confidence that it'll be reliable, it'll be a great improvement.

@wtgodbe
Copy link
Member

wtgodbe commented Mar 18, 2019

@tmds did you get a chance to do any perf testing before/after this change? Would be interesting to see what kind of improvements we're getting. Thanks a lot for doing this!

@tmds
Copy link
Member Author

tmds commented Mar 19, 2019

did you get a chance to do any perf testing before/after this change? Would be interesting to see what kind of improvements we're getting

I took the code snippit that was included in https://github.com/dotnet/corefx/issues/32174.

Pre PR:

Without carriage return in 27 ms
With carriage return in 3141 ms

Post PR:

Without carriage return in 27 ms
With carriage return in 34 ms

@@ -109,7 +126,10 @@ public static ConsoleKeyInfo ReadKey(bool intercept)
keyInfo = new ConsoleKeyInfo('\r', keyInfo.Key, shift, alt, control);
}

if (!intercept && !previouslyProcessed) Console.Write(keyInfo.KeyChar);
if (!intercept && !previouslyProcessed && keyInfo.KeyChar != '\0')
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@stephentoub @wtgodbe I've added != '\0 check here. This isn't related to the cursor caching. The StdInReader.ReadLine method also doesn't write these, and I think it is safe to filter those out here too.

Copy link
Member

@stephentoub stephentoub Mar 22, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this help?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hadn't noticed the question. It stops from doing a single byte write with '\0' that is ignored by the terminal.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, thanks. But what causes the null in the first place? Is it actually common?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For example, if you press an arrow key, you get a '\0' char.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, got it.

@stephentoub
Copy link
Member

Thanks!

@stephentoub stephentoub merged commit be7db25 into dotnet:master Mar 22, 2019
@ahsonkhan
Copy link
Member

ahsonkhan commented Mar 22, 2019

Looks like this change is causing a CI failure in new (unrelated) PRs (e.g. #36229):
https://dev.azure.com/dnceng/public/_build/results?buildId=130554

System/ConsolePal.Unix.cs(1284,29): error CS0103: The name 'DefaultConsoleBufferSize' does not exist in the current context [/Users/vsts/agent/2.148.2/work/1/s/src/System.Console/src/System.Console.csproj]

Where is this constant defined?

count > DefaultConsoleBufferSize) // limit the amount of bytes we are willing to inspect

@stephentoub
Copy link
Member

I'll fix it. Two PRs crossed.

@karelz karelz added this to the 3.0 milestone Apr 1, 2019
picenka21 pushed a commit to picenka21/runtime that referenced this pull request Feb 18, 2022
…otnet/corefx#36049)

* Console: Unix: Improve Cursor{Left,Top} performance by caching them

We keep a cached version for CursorLeft and CursorTop. When we write
data to stdout, we try to update the cached values based on the buffer
content, or we invalidate the cursor position.

A lock is used to deal with multi-threading issues. We synchronize
between some blocks using a 'version' field. This field is incremented
each time a cursor change may have occured. If the value has not
changed between two blocks, we know the the cursor is still at the same
position.

On signals that may mean the cursor position has changed (SIGCONT,
SIGCHLD, SIGWINCH) we invalidate the cached values.

* PR feedback

* lock on Console.Out and rework invalidation on signals

* fix: pass cursorVersion also in UpdateCachedCursorPosition

* Since we check invalidated before reading cached settings, there is no need to invalidate after writing

* Use GetWindowSize instead of getting WindowWidth, WindowHeight

* Update comment

* Fix swapped width and height

* ReadKey: avoid write call for '\0' characters


Commit migrated from dotnet/corefx@be7db25
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
5 participants