-
Notifications
You must be signed in to change notification settings - Fork 4.9k
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 helper method frames from Monitors #113242
base: main
Are you sure you want to change the base?
Conversation
davidwrighton
commented
Mar 7, 2025
•
edited
Loading
edited
- Convert to the FCALL fast path/QCALL slow path approach
- Make EnterHelperResult/LeaveHelperAction into enum class so that they can safely be silently marshaled between native and managed
- Move the lockTaken flag handling to managed code so we can share more helpers
- Benchmarking indicates that this makes uncontended locks somewhere around 10% faster. I believe this is primarily due to removing a small amount of handling around error checking and lock flag usage which isn't always needed.
- Convert to the FCALL fast path/QCALL slow path approach - Make EnterHelperResult/LeaveHelperAction into enum class so that they can safely be silently marshaled between native and managed - Move the lockTaken flag handling to managed code so we can share more helpers
Tagging subscribers to this area: @mangod9 |
@EgorBot -amd -arm -windows_intel using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Numerics;
BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args);
public class Tests
{
private obj _lockObj = new object();
private int _field = 0;
[Benchmark(Baseline = true)]
public string Lock_OnLockObj()
{
_field++;
lock(_lockObj)
{
_field++;
}
}
[Benchmark]
public bool LockOnThis_ThisKnownToBeNonNull()
{
_field++;
lock(this)
{
_field++;
}
}
[Benchmark]
public bool LockOnThis_ThisKnownToBeNonNull()
{
lock(this)
{
_field++;
}
}
} |
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.
PR Overview
This PR refactors the Monitor implementation to remove helper method frames by introducing a fast path/slow path approach for lock acquisition and release. Key changes include:
- Introducing new fast path methods (TryEnter_FastPath and TryEnter_FastPath_WithTimeout) and enumerated types (EnterHelperResult, LeaveHelperAction) for better handling of lock acquisition.
- Replacing the ReliableEnter/Exit methods with new logic for handling both immediate and timeout-based lock acquisition.
- Migrating lockTaken flag handling to managed code and updating related methods (Enter, TryEnter, and Exit) accordingly.
Reviewed Changes
File | Description |
---|---|
src/coreclr/System.Private.CoreLib/src/System/Threading/Monitor.CoreCLR.cs | Refactors lock acquisition and release by adding fast path/slow path methods and replacing old helper methods, while updating argument validation and exception handling |
Copilot reviewed 16 out of 16 changed files in this pull request and generated 2 comments.
TryEnter(obj, millisecondsTimeout, ref lockTaken); | ||
return lockTaken; | ||
ArgumentNullException.ThrowIfNull(obj); | ||
|
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.
The TryEnter(object, int) method does not explicitly validate that millisecondsTimeout is not less than -1. To maintain the documented contract, consider adding a check to throw ArgumentException for invalid timeout values.
if (millisecondsTimeout < -1) | |
{ | |
throw new ArgumentException("Timeout must be greater than or equal to -1.", nameof(millisecondsTimeout)); | |
} |
Copilot is powered by AI, so mistakes are possible. Review output carefully before use.
} | ||
|
||
private static void TryEnter_Timeout_WithLockTaken(object obj, int timeout, ref bool lockTaken) | ||
{ |
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.
The TryEnter_Timeout_WithLockTaken method does not handle cases where timeout is less than -1. It is recommended to add an explicit check and throw an ArgumentException when timeout is invalid.
{ | |
{ | |
if (timeout < -1) | |
{ | |
throw new ArgumentException("Timeout must be greater than or equal to -1.", nameof(timeout)); | |
} |
Copilot is powered by AI, so mistakes are possible. Review output carefully before use.
Sigh. Looks like something is busted. I'd like to finish looking into that before anyone reviews this. |
return; | ||
} | ||
|
||
Exit_Slowpath(obj, exitBehavior); |
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.
Can Exit_FastPath
do anything when returning exitBehavior != LeaveHelperAction.None
(note NOT EQUAL) that would break the subsequent fall-through to Exit_Slowpath
(e.g. we did the fast-path exit, but it needs some other LeaveHelperAction... but we've done the Exit already)?
(same question on line 154)
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.
No.
if (exitBehavior == LeaveHelperAction.None) | ||
return; | ||
|
||
Exit_Slowpath(obj, exitBehavior); |
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.
Can Exit_FastPath do anything when returning exitBehavior != LeaveHelperAction.None (note NOT EQUAL) that would break the subsequent fall-through to Exit_Slowpath (e.g. we did the fast-path exit, but it needs some other LeaveHelperAction... but we've done the Exit already)?
(same question on line 174)
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.
No.
} | ||
} | ||
|
||
if (TryEnter_Slowpath(obj, timeout)) |
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.
Will this result in up to two timeout
periods elapsing if the fast-path tried and returned anything other than Entered or Contention?
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.
No, the timeout input to the fast path is only used to check against 0 (which has special meaning).
return AwareLock::EnterHelperResult::Contention; | ||
} | ||
|
||
result = obj->EnterObjMonitorHelperSpin(pCurThread); |
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.
Should a timeOut
of non-zero be handled, should it be passed down to/honored by the EnterObjMonitorHelperSpin
?
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.
No, this is the fast set of spinning that waits for fractions of a millisecond or so, if you're willing to wait at all, we run the same amount of spinning for all possible timeouts.
…ectly. Also hard-code the exact values for the Enter/Leave results on the native side so that the values are well defined instead of implicit.
{ | ||
QCALL_CONTRACT; | ||
|
||
GCX_COOP(); |
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.
QCalls need BEGIN_QCALL
and END_QCALL
if exceptions will be thrown.
private enum LeaveHelperAction { | ||
None = 0, | ||
Signal = 1, | ||
Yield = 2, | ||
Contention = 3, | ||
Error = 4, | ||
}; |
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.
private enum LeaveHelperAction { | |
None = 0, | |
Signal = 1, | |
Yield = 2, | |
Contention = 3, | |
Error = 4, | |
}; | |
private enum LeaveHelperAction | |
{ | |
None = 0, | |
Signal = 1, | |
Yield = 2, | |
Contention = 3, | |
Error = 4, | |
} |