Skip to content

Commit 27c529a

Browse files
committed
Fixed ArrayPoolBufferWriter<T> repeated allocations
1 parent 2b610c4 commit 27c529a

File tree

2 files changed

+70
-6
lines changed

2 files changed

+70
-6
lines changed

Microsoft.Toolkit.HighPerformance/Buffers/ArrayPoolBufferWriter{T}.cs

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using System.Runtime.InteropServices;
1111
using Microsoft.Toolkit.HighPerformance.Buffers.Views;
1212
using Microsoft.Toolkit.HighPerformance.Extensions;
13+
using Microsoft.Toolkit.HighPerformance.Helpers.Internals;
1314

1415
namespace Microsoft.Toolkit.HighPerformance.Buffers
1516
{
@@ -233,15 +234,15 @@ public void Advance(int count)
233234
/// <inheritdoc/>
234235
public Memory<T> GetMemory(int sizeHint = 0)
235236
{
236-
CheckAndResizeBuffer(sizeHint);
237+
CheckBufferAndEnsureCapacity(sizeHint);
237238

238239
return this.array.AsMemory(this.index);
239240
}
240241

241242
/// <inheritdoc/>
242243
public Span<T> GetSpan(int sizeHint = 0)
243244
{
244-
CheckAndResizeBuffer(sizeHint);
245+
CheckBufferAndEnsureCapacity(sizeHint);
245246

246247
return this.array.AsSpan(this.index);
247248
}
@@ -251,7 +252,7 @@ public Span<T> GetSpan(int sizeHint = 0)
251252
/// </summary>
252253
/// <param name="sizeHint">The minimum number of items to ensure space for in <see cref="array"/>.</param>
253254
[MethodImpl(MethodImplOptions.AggressiveInlining)]
254-
private void CheckAndResizeBuffer(int sizeHint)
255+
private void CheckBufferAndEnsureCapacity(int sizeHint)
255256
{
256257
if (this.array is null)
257258
{
@@ -268,12 +269,32 @@ private void CheckAndResizeBuffer(int sizeHint)
268269
sizeHint = 1;
269270
}
270271

271-
if (sizeHint > FreeCapacity)
272+
if (sizeHint > array!.Length - this.index)
272273
{
273-
int minimumSize = this.index + sizeHint;
274+
ResizeBuffer(sizeHint);
275+
}
276+
}
274277

275-
this.pool.Resize(ref this.array, minimumSize);
278+
/// <summary>
279+
/// Resizes <see cref="array"/> to ensure it can fit the specified number of new items.
280+
/// </summary>
281+
/// <param name="sizeHint">The minimum number of items to ensure space for in <see cref="array"/>.</param>
282+
[MethodImpl(MethodImplOptions.NoInlining)]
283+
private void ResizeBuffer(int sizeHint)
284+
{
285+
int minimumSize = this.index + sizeHint;
286+
287+
// The ArrayPool<T> class has a maximum threshold of 1024 * 1024 for the maximum length of
288+
// pooled arrays, and once this is exceeded it will just allocate a new array every time
289+
// of exactly the requested size. In that case, we manually round up the requested size to
290+
// the nearest power of two, to ensure that repeated consecutive writes when the array in
291+
// use is bigger than that threshold don't end up causing a resize every single time.
292+
if (minimumSize > 1024 * 1024)
293+
{
294+
minimumSize = BitOperations.RoundUpPowerOfTwo(minimumSize);
276295
}
296+
297+
this.pool.Resize(ref this.array, minimumSize);
277298
}
278299

279300
/// <inheritdoc/>
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System.Diagnostics.Contracts;
6+
using System.Runtime.CompilerServices;
7+
#if NETCOREAPP3_1
8+
using static System.Numerics.BitOperations;
9+
#endif
10+
11+
namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
12+
{
13+
/// <summary>
14+
/// Utility methods for intrinsic bit-twiddling operations. The methods use hardware intrinsics
15+
/// when available on the underlying platform, otherwise they use optimized software fallbacks.
16+
/// </summary>
17+
internal static class BitOperations
18+
{
19+
/// <summary>
20+
/// Rounds up an <see cref="int"/> value to a power of 2.
21+
/// </summary>
22+
/// <param name="x">The input value to round up.</param>
23+
/// <returns>The smallest power of two greater than or equal to <paramref name="x"/>.</returns>
24+
[Pure]
25+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
26+
public static int RoundUpPowerOfTwo(int x)
27+
{
28+
#if NETCOREAPP3_1
29+
return 1 << (32 - LeadingZeroCount((uint)(x - 1)));
30+
#else
31+
x--;
32+
x |= x >> 1;
33+
x |= x >> 2;
34+
x |= x >> 4;
35+
x |= x >> 8;
36+
x |= x >> 16;
37+
x++;
38+
39+
return x;
40+
#endif
41+
}
42+
}
43+
}

0 commit comments

Comments
 (0)