Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 38 additions & 9 deletions src/libraries/System.Buffers/tests/ArrayPool/UnitTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using System.Numerics;
using System.Threading.Tasks;
using Microsoft.DotNet.RemoteExecutor;
using Microsoft.DotNet.XUnitExtensions;
using Xunit;

namespace System.Buffers.ArrayPool.Tests
Expand Down Expand Up @@ -240,15 +241,11 @@ public static void NewDefaultArrayPoolWithSmallBufferSizeRoundsToOurSmallestSupp
}

[Fact]
public static void ReturningABufferGreaterThanMaxSizeDoesNotThrow()
public static void ReturningToCreatePoolABufferGreaterThanMaxSizeDoesNotThrow()
{
ArrayPool<byte> pool = ArrayPool<byte>.Create(maxArrayLength: 16, maxArraysPerBucket: 1);
byte[] rented = pool.Rent(32);
pool.Return(rented);

ArrayPool<byte>.Shared.Return(new byte[3 * 1024 * 1024]);
ArrayPool<char>.Shared.Return(new char[3 * 1024 * 1024]);
ArrayPool<string>.Shared.Return(new string[3 * 1024 * 1024]);
}

[Fact]
Expand Down Expand Up @@ -292,11 +289,11 @@ public static void CanRentManySizedBuffers(ArrayPool<byte> pool)
[InlineData(1024, 1024)]
[InlineData(4096, 4096)]
[InlineData(1024 * 1024, 1024 * 1024)]
[InlineData(1024 * 1024 + 1, 1024 * 1024 + 1)]
[InlineData(1024 * 1024 + 1, 1024 * 1024 * 2)]
[InlineData(1024 * 1024 * 2, 1024 * 1024 * 2)]
public static void RentingSpecificLengthsYieldsExpectedLengths(int requestedMinimum, int expectedLength)
{
foreach (ArrayPool<byte> pool in new[] { ArrayPool<byte>.Create(), ArrayPool<byte>.Shared })
foreach (ArrayPool<byte> pool in new[] { ArrayPool<byte>.Create((int)BitOperations.RoundUpToPowerOf2((uint)requestedMinimum), 1), ArrayPool<byte>.Shared })
{
byte[] buffer1 = pool.Rent(requestedMinimum);
byte[] buffer2 = pool.Rent(requestedMinimum);
Expand All @@ -313,7 +310,7 @@ public static void RentingSpecificLengthsYieldsExpectedLengths(int requestedMini
pool.Return(buffer1);
}

foreach (ArrayPool<char> pool in new[] { ArrayPool<char>.Create(), ArrayPool<char>.Shared })
foreach (ArrayPool<char> pool in new[] { ArrayPool<char>.Create((int)BitOperations.RoundUpToPowerOf2((uint)requestedMinimum), 1), ArrayPool<char>.Shared })
{
char[] buffer1 = pool.Rent(requestedMinimum);
char[] buffer2 = pool.Rent(requestedMinimum);
Expand All @@ -330,7 +327,7 @@ public static void RentingSpecificLengthsYieldsExpectedLengths(int requestedMini
pool.Return(buffer1);
}

foreach (ArrayPool<string> pool in new[] { ArrayPool<string>.Create(), ArrayPool<string>.Shared })
foreach (ArrayPool<string> pool in new[] { ArrayPool<string>.Create((int)BitOperations.RoundUpToPowerOf2((uint)requestedMinimum), 1), ArrayPool<string>.Shared })
{
string[] buffer1 = pool.Rent(requestedMinimum);
string[] buffer2 = pool.Rent(requestedMinimum);
Expand All @@ -348,6 +345,38 @@ public static void RentingSpecificLengthsYieldsExpectedLengths(int requestedMini
}
}

[ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.Is64BitProcess))]
[InlineData(1024 * 1024 * 1024 - 1, true)]
[InlineData(1024 * 1024 * 1024, true)]
[InlineData(1024 * 1024 * 1024 + 1, false)]
[InlineData(0X7FFFFFC7 /* Array.MaxLength */, false)]
[OuterLoop]
public static void RentingGiganticArraySucceeds(int length, bool expectPooled)
{
var options = new RemoteInvokeOptions();
options.StartInfo.UseShellExecute = false;
options.StartInfo.EnvironmentVariables.Add(TrimSwitchName, "false");

RemoteExecutor.Invoke((lengthStr, expectPooledStr) =>
{
int length = int.Parse(lengthStr);
byte[] array;
try
{
array = ArrayPool<byte>.Shared.Rent(length);
}
catch (OutOfMemoryException)
{
return;
}

Assert.InRange(array.Length, length, int.MaxValue);
ArrayPool<byte>.Shared.Return(array);

Assert.Equal(bool.Parse(expectPooledStr), ReferenceEquals(array, ArrayPool<byte>.Shared.Rent(length)));
}, length.ToString(), expectPooled.ToString(), options).Dispose();
}

[Fact]
public static void RentingAfterPoolExhaustionReturnsSizeForCorrespondingBucket_SmallerThanLimit()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,11 @@ public ReadOnlySpan<byte> Deflate(ReadOnlySpan<byte> payload, bool endOfMessage)
{
Debug.Assert(_buffer is null, "Invalid state, ReleaseBuffer not called.");

// Do not try to rent more than 1MB initially, because it will actually allocate
// instead of renting. Be optimistic that what we're sending is actually going to fit.
const int MaxInitialBufferLength = 1024 * 1024;

// For small payloads there might actually be overhead in the compression and the resulting
// output might be larger than the payload. This is why we rent at least 4KB initially.
const int MinInitialBufferLength = 4 * 1024;

_buffer = ArrayPool<byte>.Shared.Rent(Math.Clamp(payload.Length, MinInitialBufferLength, MaxInitialBufferLength));
_buffer = ArrayPool<byte>.Shared.Rent(Math.Max(payload.Length, MinInitialBufferLength));
int position = 0;

while (true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,9 @@ public void Prepare(long payloadLength, int userBufferLength)
}
else
{
// Rent a buffer as close to the size of the user buffer as possible,
// but not try to rent anything above 1MB because the array pool will allocate.
// Rent a buffer as close to the size of the user buffer as possible.
// If the payload is smaller than the user buffer, rent only as much as we need.
_buffer = ArrayPool<byte>.Shared.Rent(Math.Min(userBufferLength, (int)Math.Min(payloadLength, 1024 * 1024)));
_buffer = ArrayPool<byte>.Shared.Rent((int)Math.Min(userBufferLength, payloadLength));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,11 @@ internal sealed partial class TlsOverPerCoreLockedStacksArrayPool<T> : ArrayPool
// TODO https://github.com/dotnet/coreclr/pull/7747: "Investigate optimizing ArrayPool heuristics"
// - Explore caching in TLS more than one array per size per thread, and moving stale buffers to the global queue.
// - Explore changing the size of each per-core bucket, potentially dynamically or based on other factors like array size.
// - Explore changing number of buckets and what sizes of arrays are cached.
// - Investigate whether false sharing is causing any issues, in particular on LockedStack's count and the contents of its array.
// ...

/// <summary>The number of buckets (array sizes) in the pool, one for each array length, starting from length 16.</summary>
private const int NumBuckets = 17; // Utilities.SelectBucketIndex(2*1024*1024)
private const int NumBuckets = 27; // Utilities.SelectBucketIndex(1024 * 1024 * 1024 + 1)
/// <summary>Maximum number of per-core stacks to use per array size.</summary>
private const int MaxPerCorePerArraySizeStacks = 64; // selected to avoid needing to worry about processor groups
/// <summary>The maximum number of buffers to store in a bucket's global queue.</summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -722,7 +722,9 @@ private unsafe int IcuGetHashCodeOfString(ReadOnlySpan<char> source, CompareOpti

// according to ICU User Guide the performance of ucol_getSortKey is worse when it is called with null output buffer
// the solution is to try to fill the sort key in a temporary buffer of size equal 4 x string length
// 1MB is the biggest array that can be rented from ArrayPool.Shared without memory allocation
// (The ArrayPool used to have a limit on the length of buffers it would cache; this code was avoiding
// exceeding that limit to avoid a per-operation allocation, and the performance implications here
// were not re-evaluated when the limit was lifted.)
int sortKeyLength = (source.Length > 1024 * 1024 / 4) ? 0 : 4 * source.Length;

byte[]? borrowedArray = null;
Expand Down