@@ -48,25 +48,56 @@ public static async IAsyncEnumerable<TSource[]> ChunkAsync<TSource>(this IAsyncE
48
48
49
49
await using var e = source . GetConfiguredAsyncEnumerator ( cancellationToken , false ) ;
50
50
51
+ // Before allocating anything, make sure there's at least one element.
51
52
if ( await e . MoveNextAsync ( ) )
52
53
{
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
55
60
{
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 )
57
68
{
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
+ }
61
77
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
+ }
63
92
64
- if ( chunkBuilder . Count < size || ! await e . MoveNextAsync ( ) )
93
+ if ( i != array . Length )
65
94
{
66
- yield break ;
95
+ Array . Resize ( ref array , i ) ;
67
96
}
68
- chunkBuilder . Clear ( ) ;
97
+
98
+ yield return array ;
69
99
}
100
+ while ( i >= size && await e . MoveNextAsync ( ) ) ;
70
101
}
71
102
}
72
103
}
0 commit comments