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

Bring back old array enumerator code #88371

Merged
merged 4 commits into from
Jul 7, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1099,7 +1099,10 @@ private Array() { }
public new IEnumerator<T> GetEnumerator()
{
T[] @this = Unsafe.As<T[]>(this);
return @this.Length == 0 ? SZGenericArrayEnumerator<T>.Empty : new SZGenericArrayEnumerator<T>(@this);
// get length so we don't have to call the Length property again in ArrayEnumerator constructor
// and avoid more checking there too.
int length = @this.Length;
return length == 0 ? SZGenericArrayEnumerator<T>.Empty : new SZGenericArrayEnumerator<T>(@this, length);
}

public int Count
Expand Down Expand Up @@ -1196,6 +1199,68 @@ public void RemoveAt(int index)
}
}

internal class SZGenericArrayEnumeratorBase : IDisposable
{
protected int _index;
protected int _endIndex;

internal SZGenericArrayEnumeratorBase()
Copy link
Contributor

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?

{
_index = -1;
}

public bool MoveNext()
{
if (_index < _endIndex)
{
_index++;
return (_index < _endIndex);
}
return false;
}

public void Dispose()
{
}
}

internal sealed class SZGenericArrayEnumerator<T> : SZGenericArrayEnumeratorBase, IEnumerator<T>
{
private readonly T[] _array;

// Passing -1 for endIndex so that MoveNext always returns false without mutating _index
internal static readonly SZGenericArrayEnumerator<T> Empty = new SZGenericArrayEnumerator<T>(null, -1);

internal SZGenericArrayEnumerator(T[] array, int endIndex)
{
_array = array;
_endIndex = endIndex;
}

public T Current
{
get
{
if ((uint)_index >= (uint)_endIndex)
ThrowHelper.ThrowInvalidOperationException();
return _array[_index];
Copy link
Contributor

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?

Copy link
Member

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.

Copy link
Contributor

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.

Copy link
Contributor

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.

}
}

object IEnumerator.Current
Copy link
Contributor

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;

{
get
{
return Current;
}
}

void IEnumerator.Reset()
Copy link
Member

@jkotas jkotas Jul 4, 2023

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?

{
_index = -1;
}
}

public class MDArray
{
public const int MinRank = 1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ public void Reset()
}
}

// Native AOT doesn't use these for size on disk reasons
#if !NATIVEAOT
internal abstract class SZGenericArrayEnumeratorBase : IDisposable
{
protected readonly Array _array;
Expand Down Expand Up @@ -140,6 +142,7 @@ public T Current

object? IEnumerator.Current => Current;
}
#endif

internal abstract class GenericEmptyEnumeratorBase : IDisposable, IEnumerator
{
Expand Down