-
Notifications
You must be signed in to change notification settings - Fork 4.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
Bring back old array enumerator code #88371
Conversation
This is a 0.3% size saving for Stage1. We not only allow array enumerators to be preinitialized again, but also avoid introducing many array `MethodTables` (looks like the new enumerators force array MethodTables for cases where we could have avoided them). I shaped the old code to look like after the refactor in dotnet#82751, but reintroduced the old classes. Fixes dotnet#82993.
Tagging subscribers to this area: @agocke, @MichalStrehovsky, @jkotas Issue DetailsThis is a 0.3% size saving for Stage1. We not only allow array enumerators to be preinitialized again, but also avoid introducing many array I shaped the old code to look like after the refactor in #82751, but reintroduced the old classes. Fixes #82993. Cc @dotnet/ilc-contrib
|
} | ||
} | ||
|
||
void IEnumerator.Reset() |
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 this be in the base class - same as in the shared one?
What are those cases? The array enumerator should be only reachable if we have created the array. |
What would be the downside of using the native AOT enumerator everywhere? The only measurable downside that I can see is that the enumerator bbject would be 4 bytes larger on 32-bit platforms that we do not care about a whole lot. |
{ | ||
if ((uint)_index >= (uint)_endIndex) | ||
ThrowHelper.ThrowInvalidOperationException(); | ||
return _array[_index]; |
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 will bounds check on every access, should this maybe use GetArrayDataReference
and Unsafe.Add
to avoid doing so?
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.
Is this enumerator used often enough on hot paths for it to make a meaningful difference?
In general, we have stayed away from manual bounds-check elimination using unsafe code in collections.
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.
Is this enumerator used often enough on hot paths for it to make a meaningful difference?
I'd say that arrays are fairly often passed to methods taking IEnumerable<T>
and this would regress all such usages.
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.
In general, we have stayed away from manual bounds-check elimination using unsafe code in collections.
Looks like we have an explicit bounds check on line 1244 now, so eliminating the one built into array accesses would be safe and worthwhile here.
protected int _index; | ||
protected int _endIndex; | ||
|
||
internal SZGenericArrayEnumeratorBase() |
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.
would it be better to capture the _endIndex in the construction as the whole point of this base class is to inline and read-only the the length?
} | ||
} | ||
|
||
object IEnumerator.Current |
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.
Is this style change now preferred to the other way in line 143?
object? IEnumerator.Current => Current;
Generic dictionaries end up referring to the MethodTable in the diff I was looking at. I didn't investigate how exactly, but it's plausible in the case where the scanner ended up scanning more stuff than ended up needed.
If we don't care about that, I can unify this. I only wanted to restore the old code to avoid sidetracking this PR into any sort of runtime perf conversation - this is restoring code that Native AOT shipped with on .NET 7 so it doesn't do anything with runtime perf there. If we do this for all runtimes, this suddenly becomes a runtime perf conversation. The point of this PR is just to fix the size regression. |
Could you please try to do this for all runtimes without ifdef? |
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.
Nice
src/libraries/System.Private.CoreLib/src/System/Array.Enumerators.cs
Outdated
Show resolved
Hide resolved
_index = -1; | ||
_endIndex = endIndex; | ||
} | ||
|
||
public bool MoveNext() |
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.
public bool MoveNext()
{
int index = _index + 1;
if ((uint)index < (uint)_endIndex)
{
_index = index;
return true;
}
_index = _endIndex;
return false;
}
This should be better, on some architectures at least. (One less comparison.)
I have run a quick microbenchmark
called on Surprisingly, this implementation is faster on this microbenchmark on my machine. |
src/libraries/System.Private.CoreLib/src/System/Array.Enumerators.cs
Outdated
Show resolved
Hide resolved
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.
Thanks
Co-authored-by: Jan Kotas <jkotas@microsoft.com>
Nice, thanks for measuring! That's an unexpected bonus. The failures are all #88499, merging. |
This is a 0.3% size saving for Stage1. We not only allow array enumerators to be preinitialized again, but also avoid introducing many array
MethodTables
(looks like the new enumerators force arrayMethodTable
for cases where we could have avoided them).I shaped the old code to look like after the refactor in #82751, but reintroduced the old classes.
Fixes #82993.
Cc @dotnet/ilc-contrib