Skip to content

Commit 26768a5

Browse files
committed
Optimized array enumerables
1 parent c7a2d67 commit 26768a5

File tree

2 files changed

+155
-213
lines changed

2 files changed

+155
-213
lines changed

Microsoft.Toolkit.HighPerformance/Enumerables/Array2DColumnEnumerable{T}.cs

Lines changed: 99 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,35 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
1616
/// </summary>
1717
/// <typeparam name="T">The type of items to enumerate.</typeparam>
1818
[EditorBrowsable(EditorBrowsableState.Never)]
19-
public readonly ref struct Array2DColumnEnumerable<T>
19+
public ref struct Array2DColumnEnumerable<T>
2020
{
21+
#if SPAN_RUNTIME_SUPPORT
22+
/// <summary>
23+
/// The <see cref="Span{T}"/> instance mapping the target 2D array.
24+
/// </summary>
25+
/// <remarks>
26+
/// In runtimes where we have support for the <see cref="Span{T}"/> type, we can
27+
/// create one from the input 2D array and use that to traverse the target column.
28+
/// This reduces the number of operations to perform for the offsetting to the right
29+
/// column element (we simply need to add <see cref="width"/> to the offset at each
30+
/// iteration to move down by one row), and allows us to use the fast <see cref="Span{T}"/>
31+
/// accessor instead of the slower indexer for 2D arrays, as we can then access each
32+
/// individual item linearly, since we know the absolute offset from the base location.
33+
/// </remarks>
34+
private readonly Span<T> span;
35+
36+
/// <summary>
37+
/// The width of the target 2D array.
38+
/// </summary>
39+
private readonly int width;
40+
41+
/// <summary>
42+
/// The current absolute offset within <see cref="span"/>.
43+
/// </summary>
44+
private int offset;
45+
#else
2146
/// <summary>
22-
/// The source 2D <typeparamref name="T"/> array instance.
47+
/// The source 2D array instance.
2348
/// </summary>
2449
private readonly T[,] array;
2550

@@ -28,24 +53,93 @@ public readonly ref struct Array2DColumnEnumerable<T>
2853
/// </summary>
2954
private readonly int column;
3055

56+
/// <summary>
57+
/// The height of a column in <see cref="array"/>.
58+
/// </summary>
59+
private readonly int height;
60+
61+
/// <summary>
62+
/// The current row.
63+
/// </summary>
64+
private int row;
65+
#endif
66+
3167
/// <summary>
3268
/// Initializes a new instance of the <see cref="Array2DColumnEnumerable{T}"/> struct.
3369
/// </summary>
34-
/// <param name="array">The source 2D <typeparamref name="T"/> array instance.</param>
70+
/// <param name="array">The source 2D array instance.</param>
3571
/// <param name="column">The target column to iterate within <paramref name="array"/>.</param>
3672
[MethodImpl(MethodImplOptions.AggressiveInlining)]
3773
public Array2DColumnEnumerable(T[,] array, int column)
3874
{
75+
if ((uint)column >= (uint)array.GetLength(1))
76+
{
77+
ThrowArgumentOutOfRangeExceptionForInvalidColumn();
78+
}
79+
80+
#if SPAN_RUNTIME_SUPPORT
81+
this.span = array.AsSpan();
82+
this.width = array.GetLength(1);
83+
this.offset = column - this.width;
84+
#else
3985
this.array = array;
4086
this.column = column;
87+
this.height = array.GetLength(0);
88+
this.row = -1;
89+
#endif
4190
}
4291

4392
/// <summary>
4493
/// Implements the duck-typed <see cref="IEnumerable{T}.GetEnumerator"/> method.
4594
/// </summary>
46-
/// <returns>An <see cref="Enumerator"/> instance targeting the current 2D <typeparamref name="T"/> array instance.</returns>
95+
/// <returns>An <see cref="Array2DColumnEnumerable{T}"/> instance targeting the current 2D <typeparamref name="T"/> array instance.</returns>
4796
[MethodImpl(MethodImplOptions.AggressiveInlining)]
48-
public Enumerator GetEnumerator() => new Enumerator(this.array, this.column);
97+
public Array2DColumnEnumerable<T> GetEnumerator() => this;
98+
99+
/// <summary>
100+
/// Implements the duck-typed <see cref="System.Collections.IEnumerator.MoveNext"/> method.
101+
/// </summary>
102+
/// <returns><see langword="true"/> whether a new element is available, <see langword="false"/> otherwise</returns>
103+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
104+
public bool MoveNext()
105+
{
106+
#if SPAN_RUNTIME_SUPPORT
107+
int offset = this.offset + this.width;
108+
109+
if ((uint)offset < (uint)this.span.Length)
110+
{
111+
this.offset = offset;
112+
113+
return true;
114+
}
115+
#else
116+
int row = this.row + 1;
117+
118+
if (row < this.height)
119+
{
120+
this.row = row;
121+
122+
return true;
123+
}
124+
#endif
125+
return false;
126+
}
127+
128+
/// <summary>
129+
/// Gets the duck-typed <see cref="IEnumerator{T}.Current"/> property.
130+
/// </summary>
131+
public ref T Current
132+
{
133+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
134+
get
135+
{
136+
#if SPAN_RUNTIME_SUPPORT
137+
return ref this.span.DangerousGetReferenceAt(this.offset);
138+
#else
139+
return ref this.array[this.row, this.column];
140+
#endif
141+
}
142+
}
49143

50144
/// <summary>
51145
/// Returns a <typeparamref name="T"/> array with the values in the target column.
@@ -79,129 +173,6 @@ public T[] ToArray()
79173
return array;
80174
}
81175

82-
/// <summary>
83-
/// An enumerator for a source 2D array instance.
84-
/// </summary>
85-
[EditorBrowsable(EditorBrowsableState.Never)]
86-
public ref struct Enumerator
87-
{
88-
#if SPAN_RUNTIME_SUPPORT
89-
/// <summary>
90-
/// The <see cref="Span{T}"/> instance mapping the target 2D array.
91-
/// </summary>
92-
/// <remarks>
93-
/// In runtimes where we have support for the <see cref="Span{T}"/> type, we can
94-
/// create one from the input 2D array and use that to traverse the target column.
95-
/// This reduces the number of operations to perform for the offsetting to the right
96-
/// column element (we simply need to add <see cref="width"/> to the offset at each
97-
/// iteration to move down by one row), and allows us to use the fast <see cref="Span{T}"/>
98-
/// accessor instead of the slower indexer for 2D arrays, as we can then access each
99-
/// individual item linearly, since we know the absolute offset from the base location.
100-
/// </remarks>
101-
private readonly Span<T> span;
102-
103-
/// <summary>
104-
/// The width of the target 2D array.
105-
/// </summary>
106-
private readonly int width;
107-
108-
/// <summary>
109-
/// The current absolute offset within <see cref="span"/>.
110-
/// </summary>
111-
private int offset;
112-
#else
113-
/// <summary>
114-
/// The source 2D array instance.
115-
/// </summary>
116-
private readonly T[,] array;
117-
118-
/// <summary>
119-
/// The target column to iterate within <see cref="array"/>.
120-
/// </summary>
121-
private readonly int column;
122-
123-
/// <summary>
124-
/// The height of a column in <see cref="array"/>.
125-
/// </summary>
126-
private readonly int height;
127-
128-
/// <summary>
129-
/// The current row.
130-
/// </summary>
131-
private int row;
132-
#endif
133-
134-
/// <summary>
135-
/// Initializes a new instance of the <see cref="Enumerator"/> struct.
136-
/// </summary>
137-
/// <param name="array">The source 2D array instance.</param>
138-
/// <param name="column">The target column to iterate within <paramref name="array"/>.</param>
139-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
140-
public Enumerator(T[,] array, int column)
141-
{
142-
if ((uint)column >= (uint)array.GetLength(1))
143-
{
144-
ThrowArgumentOutOfRangeExceptionForInvalidColumn();
145-
}
146-
147-
#if SPAN_RUNTIME_SUPPORT
148-
this.span = array.AsSpan();
149-
this.width = array.GetLength(1);
150-
this.offset = column - this.width;
151-
#else
152-
this.array = array;
153-
this.column = column;
154-
this.height = array.GetLength(0);
155-
this.row = -1;
156-
#endif
157-
}
158-
159-
/// <summary>
160-
/// Implements the duck-typed <see cref="System.Collections.IEnumerator.MoveNext"/> method.
161-
/// </summary>
162-
/// <returns><see langword="true"/> whether a new element is available, <see langword="false"/> otherwise</returns>
163-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
164-
public bool MoveNext()
165-
{
166-
#if SPAN_RUNTIME_SUPPORT
167-
int offset = this.offset + this.width;
168-
169-
if ((uint)offset < (uint)this.span.Length)
170-
{
171-
this.offset = offset;
172-
173-
return true;
174-
}
175-
#else
176-
int row = this.row + 1;
177-
178-
if (row < this.height)
179-
{
180-
this.row = row;
181-
182-
return true;
183-
}
184-
#endif
185-
return false;
186-
}
187-
188-
/// <summary>
189-
/// Gets the duck-typed <see cref="IEnumerator{T}.Current"/> property.
190-
/// </summary>
191-
public ref T Current
192-
{
193-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
194-
get
195-
{
196-
#if SPAN_RUNTIME_SUPPORT
197-
return ref this.span.DangerousGetReferenceAt(this.offset);
198-
#else
199-
return ref this.array[this.row, this.column];
200-
#endif
201-
}
202-
}
203-
}
204-
205176
/// <summary>
206177
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the <see cref="column"/> is invalid.
207178
/// </summary>

0 commit comments

Comments
 (0)