Skip to content

Commit e2f29d5

Browse files
committed
1 parent f8a1797 commit e2f29d5

File tree

1 file changed

+41
-10
lines changed
  • Ix.NET/Source/System.Linq.Async/System/Linq/Operators

1 file changed

+41
-10
lines changed

Ix.NET/Source/System.Linq.Async/System/Linq/Operators/Chunk.cs

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -48,25 +48,56 @@ public static async IAsyncEnumerable<TSource[]> ChunkAsync<TSource>(this IAsyncE
4848

4949
await using var e = source.GetConfiguredAsyncEnumerator(cancellationToken, false);
5050

51+
// Before allocating anything, make sure there's at least one element.
5152
if (await e.MoveNextAsync())
5253
{
53-
var chunkBuilder = new List<TSource>();
54-
while (true)
54+
// Now that we know we have at least one item, allocate an initial storage array. This is not
55+
// the array we'll yield. It starts out small in order to avoid significantly overallocating
56+
// when the source has many fewer elements than the chunk size.
57+
var arraySize = Math.Min(size, 4);
58+
int i;
59+
do
5560
{
56-
do
61+
var array = new TSource[arraySize];
62+
63+
// Store the first item.
64+
array[0] = e.Current;
65+
i = 1;
66+
67+
if (size != array.Length)
5768
{
58-
chunkBuilder.Add(e.Current);
59-
}
60-
while (chunkBuilder.Count < size && await e.MoveNextAsync());
69+
// This is the first chunk. As we fill the array, grow it as needed.
70+
for (; i < size && await e.MoveNextAsync(); i++)
71+
{
72+
if (i >= array.Length)
73+
{
74+
arraySize = (int)Math.Min((uint)size, 2 * (uint)array.Length);
75+
Array.Resize(ref array, arraySize);
76+
}
6177

62-
yield return chunkBuilder.ToArray();
78+
array[i] = e.Current;
79+
}
80+
}
81+
else
82+
{
83+
// For all but the first chunk, the array will already be correctly sized.
84+
// We can just store into it until either it's full or MoveNext returns false.
85+
var local = array; // avoid bounds checks by using cached local (`array` is lifted to iterator object as a field)
86+
Debug.Assert(local.Length == size);
87+
for (; (uint)i < (uint)local.Length && await e.MoveNextAsync(); i++)
88+
{
89+
local[i] = e.Current;
90+
}
91+
}
6392

64-
if (chunkBuilder.Count < size || !await e.MoveNextAsync())
93+
if (i != array.Length)
6594
{
66-
yield break;
95+
Array.Resize(ref array, i);
6796
}
68-
chunkBuilder.Clear();
97+
98+
yield return array;
6999
}
100+
while (i >= size && await e.MoveNextAsync());
70101
}
71102
}
72103
}

0 commit comments

Comments
 (0)