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

Console.OpenStandard* few times slower on Linux #31396

Closed
adamsitnik opened this issue Nov 5, 2019 · 6 comments
Closed

Console.OpenStandard* few times slower on Linux #31396

adamsitnik opened this issue Nov 5, 2019 · 6 comments
Labels
area-System.Console help wanted [up-for-grabs] Good issue for external contributors os-linux Linux OS (any supported distro) tenet-performance Performance related issue
Milestone

Comments

@adamsitnik
Copy link
Member

Console.OpenStandard* methods are few times slower on Linux.

Slower diff/base Base Median (ns) Diff Median (ns) Modality
System.ConsoleTests.Perf_Console.OpenStandardInput 10.50 175.08 1838.35
System.ConsoleTests.Perf_Console.OpenStandardError 4.25 434.18 1846.05
System.ConsoleTests.Perf_Console.OpenStandardOutput 4.21 437.84 1843.03

How to run the benchmarks:

git clone https://github.com/dotnet/performance.git
python3 ./performance/scripts/benchmarks_ci.py -f netcoreapp5.0 --filter 'System.ConsoleTests.Perf_Console.OpenStandard*'

Recommended profilers are PerfCollect and VTune.

@adamsitnik
Copy link
Member Author

Quick look at the profile:

private static void Main(string[] args)
{
    for (int i = 0; i < 10_000_000; i++)
    {
        using (var output = Console.OpenStandardOutput())
        {
        };    
    }
}

image

20% of time is spent in call to fstat:

https://github.com/dotnet/corefx/blob/ee038925bde85c1c45c52f235ab09ea03b9c126c/src/System.Console/src/System/ConsolePal.Unix.cs#L1427-L1430

Which sets _handleType which looks like is not used anymore (it might be a low hanging fruit)

jkotas referenced this issue in jkotas/corefx Nov 6, 2019
Contributes to #42378
stephentoub referenced this issue in dotnet/corefx Nov 6, 2019
@msftgits msftgits transferred this issue from dotnet/corefx Feb 1, 2020
@maryamariyan maryamariyan added the untriaged New issue has not been triaged by the area owner label Feb 23, 2020
@adamsitnik adamsitnik added this to the Future milestone Jun 24, 2020
@adamsitnik adamsitnik removed the untriaged New issue has not been triaged by the area owner label Jun 24, 2020
@tmds
Copy link
Member

tmds commented Jul 3, 2020

Which sets _handleType which looks like is not used anymore (it might be a low hanging fruit)

@adamsitnik this issue can be closed. _handleType was removed in dotnet/corefx#42400.

@adamsitnik
Copy link
Member Author

@tmds When I created this issue I was hoping that somebody is going to perform a full investigation and bring the performance on par with Windows. We have solved only the low hanging fruit (20%) and the difference is still very big. That's why I would perfer to keep it open.

@tmds
Copy link
Member

tmds commented Jul 3, 2020

Digging a bit deeper.

The highest cost comes from the syscalls involved:

Windows makes 1 for open, and Dispose doesn't make any:

protected override void Dispose(bool disposing)
{
// We're probably better off not closing the OS handle here. First,
// we allow a program to get multiple instances of ConsoleStreams
// around the same OS handle, so closing one handle would invalidate
// them all. Additionally, we want a second AppDomain to be able to
// write to stdout if a second AppDomain quits.
_handle = IntPtr.Zero;
base.Dispose(disposing);
}

Unix makes 1 for open, and Dispose makes 2:

protected override bool ReleaseHandle()
{
// When the SafeFileHandle was opened, we likely issued an flock on the created descriptor in order to add
// an advisory lock. This lock should be removed via closing the file descriptor, but close can be
// interrupted, and we don't retry closes. As such, we could end up leaving the file locked,
// which could prevent subsequent usage of the file until this process dies. To avoid that, we proactively
// try to release the lock before we close the handle. (If it's not locked, there's no behavioral
// problem trying to unlock it.)
Interop.Sys.FLock(handle, Interop.Sys.LockOperations.LOCK_UN); // ignore any errors
// Close the descriptor. Although close is documented to potentially fail with EINTR, we never want
// to retry, as the descriptor could actually have been closed, been subsequently reassigned, and
// be in use elsewhere in the process. Instead, we simply check whether the call was successful.
int result = Interop.Sys.Close(handle);
Debug.Assert(result == 0, $"Close failed with result {result} and error {Interop.Sys.GetLastErrorInfo()}");
return result == 0;
}

The flock could be eliminated. It would require specializing the Handle type. I don't think it is worth it because I expect this won't be called on perf critical paths. For best performance, the method should be called once, and the returned Stream should be kept around.

@tmds
Copy link
Member

tmds commented Jul 14, 2020

@adamsitnik is the above analysis good enough to close this issue?

@adamsitnik
Copy link
Member Author

is the above analysis good enough to close this issue?

It is! Thank you @tmds!

@adamsitnik adamsitnik closed this as not planned Won't fix, can't repro, duplicate, stale Nov 13, 2023
@github-actions github-actions bot locked and limited conversation to collaborators Dec 14, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-System.Console help wanted [up-for-grabs] Good issue for external contributors os-linux Linux OS (any supported distro) tenet-performance Performance related issue
Projects
None yet
Development

No branches or pull requests

3 participants