@@ -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