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
Original file line number Diff line number Diff line change
Expand Up @@ -59,55 +59,5 @@ public static void Resize<T>(this ArrayPool<T> pool, ref T[]? array, int newSize

array = newArray;
}

/// <summary>
/// Changes the number of elements of an <see cref="IMemoryOwner{T}"/> instance to the specified new size.
/// </summary>
/// <typeparam name="T">The type of items into the target buffer to resize.</typeparam>
/// <param name="pool">The target <see cref="MemoryPool{T}"/> instance to use to resize the buffer.</param>
/// <param name="memoryOwner">The rented <see cref="IMemoryOwner{T}"/> instance to resize, or <see langword="null"/> to create a new one.</param>
/// <param name="newSize">The size of the new buffer.</param>
/// <param name="clearBuffer">Indicates whether the contents of the buffer should be cleared before reuse.</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="newSize"/> is less than 0.</exception>
/// <remarks>When this method returns, the caller must not use any references to the old buffer anymore.</remarks>
public static void Resize<T>(this MemoryPool<T> pool, ref IMemoryOwner<T>? memoryOwner, int newSize, bool clearBuffer = false)
{
if (newSize < 0)
{
throw new ArgumentOutOfRangeException(nameof(newSize), "The new size can't be less than 0");
}

// If the old memory is null, just create a new one with the requested size
if (memoryOwner is null)
{
memoryOwner = pool.Rent(newSize);

return;
}

// If the new size is the same as the current size, do nothing
var memory = memoryOwner.Memory;
if (memory.Length == newSize)
{
return;
}

// Same behavior as the T[] resize extension
var newMemoryOwner = pool.Rent(newSize);
int itemsToCopy = Math.Min(memory.Length, newSize);

memory.Slice(0, itemsToCopy).CopyTo(newMemoryOwner.Memory);

// Clear the original buffer, if needed
if (clearBuffer)
{
memory.Span.Clear();
}

// There isn't a return API for the memory pool, so just invoke Dispose directly
memoryOwner.Dispose();

memoryOwner = newMemoryOwner;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@
<PackageReference Include="System.Memory" Version="4.5.3" />
</ItemGroup>

<!-- .NET Standard 2.1 has both Span<T> and HashCode built-in, but lacks the Unsafe class-->
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.1' ">
<ItemGroup>
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.7.0" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public readonly ref struct ReadOnlyByReference<T>
/// <param name="offset">The target offset within <paramref name="owner"/> for the target reference.</param>
/// <remarks>The <paramref name="offset"/> parameter is not validated, and it's responsability of the caller to ensure it's valid.</remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ReadOnlyByReference(object owner, int offset)
private ReadOnlyByReference(object owner, int offset)
{
this.owner = owner;
this.offset = offset;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Runtime.CompilerServices;
using Microsoft.Toolkit.HighPerformance.Extensions;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace UnitTests.HighPerformance.Extensions
{
[TestClass]
public class Test_ArrayExtensions
{
[TestCategory("ArrayExtensions")]
[TestMethod]
public void Test_ArrayExtensions_DangerousGetReference()
{
string[] tokens = "aa,bb,cc,dd,ee,ff,gg,hh,ii".Split(',');

ref string r0 = ref Unsafe.AsRef(tokens.DangerousGetReference());
ref string r1 = ref Unsafe.AsRef(tokens[0]);

Assert.IsTrue(Unsafe.AreSame(ref r0, ref r1));
}

[TestCategory("ArrayExtensions")]
[TestMethod]
public void Test_ArrayExtensions_DangerousGetReferenceAt_Zero()
{
string[] tokens = "aa,bb,cc,dd,ee,ff,gg,hh,ii".Split(',');

ref string r0 = ref Unsafe.AsRef(tokens.DangerousGetReference());
ref string r1 = ref Unsafe.AsRef(tokens.DangerousGetReferenceAt(0));

Assert.IsTrue(Unsafe.AreSame(ref r0, ref r1));
}

[TestCategory("ArrayExtensions")]
[TestMethod]
public void Test_ArrayExtensions_DangerousGetReferenceAt_Index()
{
string[] tokens = "aa,bb,cc,dd,ee,ff,gg,hh,ii".Split(',');

ref string r0 = ref Unsafe.AsRef(tokens.DangerousGetReferenceAt(5));
ref string r1 = ref Unsafe.AsRef(tokens[5]);

Assert.IsTrue(Unsafe.AreSame(ref r0, ref r1));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Buffers;
using System.Linq;
using Microsoft.Toolkit.HighPerformance.Extensions;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace UnitTests.HighPerformance.Extensions
{
[TestClass]
public class Test_ArrayPoolExtensions
{
[TestCategory("ArrayPoolExtensions")]
[TestMethod]
[ExpectedException(typeof(ArgumentOutOfRangeException))]
public void Test_ArrayExtensions_InvalidSize()
{
int[] array = null;

ArrayPool<int>.Shared.Resize(ref array, -1);
}

[TestCategory("ArrayPoolExtensions")]
[TestMethod]
public void Test_ArrayExtensions_NewArray()
{
int[] array = null;

ArrayPool<int>.Shared.Resize(ref array, 10);

Assert.IsNotNull(array);
Assert.IsTrue(array.Length >= 10);
}

[TestCategory("ArrayPoolExtensions")]
[TestMethod]
public void Test_ArrayExtensions_SameSize()
{
int[] array = ArrayPool<int>.Shared.Rent(10);
int[] backup = array;

ArrayPool<int>.Shared.Resize(ref array, array.Length);

Assert.AreSame(array, backup);
}

[TestCategory("ArrayPoolExtensions")]
[TestMethod]
public void Test_ArrayExtensions_Expand()
{
int[] array = ArrayPool<int>.Shared.Rent(16);
int[] backup = array;

array.AsSpan().Fill(7);

ArrayPool<int>.Shared.Resize(ref array, 32);

Assert.AreNotSame(array, backup);
Assert.IsTrue(array.Length >= 32);
Assert.IsTrue(array.AsSpan(0, 16).ToArray().All(i => i == 7));
}

[TestCategory("ArrayPoolExtensions")]
[TestMethod]
public void Test_ArrayExtensions_Shrink()
{
int[] array = ArrayPool<int>.Shared.Rent(32);
int[] backup = array;

array.AsSpan().Fill(7);

ArrayPool<int>.Shared.Resize(ref array, 16);

Assert.AreNotSame(array, backup);
Assert.IsTrue(array.Length >= 16);
Assert.IsTrue(array.AsSpan(0, 16).ToArray().All(i => i == 7));
}

[TestCategory("ArrayPoolExtensions")]
[TestMethod]
public void Test_ArrayExtensions_Clear()
{
int[] array = ArrayPool<int>.Shared.Rent(16);
int[] backup = array;

array.AsSpan().Fill(7);

ArrayPool<int>.Shared.Resize(ref array, 0, true);

Assert.AreNotSame(array, backup);
Assert.IsTrue(backup.AsSpan(0, 16).ToArray().All(i => i == 0));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Diagnostics.Contracts;
using System.Runtime.InteropServices;
using Microsoft.Toolkit.HighPerformance.Extensions;

namespace UnitTests.HighPerformance.Extensions
{
public partial class Test_ReadOnlySpanExtensions
{
/// <summary>
/// Gets the list of counts to test the extension for
/// </summary>
private static ReadOnlySpan<int> TestCounts => new[] { 0, 1, 7, 128, 255, 256, short.MaxValue, short.MaxValue + 1, 123_938, 1_678_922, 71_890_819 };

[TestCategory("ReadOnlySpanExtensions")]
[TestMethod]
public void Test_ReadOnlySpanExtensions_RandomCount8()
{
TestForType((byte)123, CreateRandomData<byte>);
TestForType((sbyte)123, CreateRandomData<sbyte>);
TestForType(true, CreateRandomData<bool>);
}

[TestCategory("ReadOnlySpanExtensions")]
[TestMethod]
public void Test_ReadOnlySpanExtensions_RandomCount16()
{
TestForType((ushort)4712, CreateRandomData<ushort>);
TestForType((short)4712, CreateRandomData<short>);
TestForType((char)4712, CreateRandomData<char>);
}

[TestCategory("ReadOnlySpanExtensions")]
[TestMethod]
public void Test_ReadOnlySpanExtensions_RandomCount32()
{
TestForType((int)37438941, CreateRandomData<int>);
TestForType((uint)37438941, CreateRandomData<uint>);
}

[TestCategory("ReadOnlySpanExtensions")]
[TestMethod]
public void Test_ReadOnlySpanExtensions_RandomCount64()
{
TestForType((long)47128480128401, CreateRandomData<long>);
TestForType((ulong)47128480128401, CreateRandomData<ulong>);
}

[TestCategory("ReadOnlySpanExtensions")]
[TestMethod]
public void Test_ReadOnlySpanExtensions_FilledCount8()
{
TestForType((byte)123, count => CreateFilledData(count, (byte)123));
TestForType((sbyte)123, count => CreateFilledData(count, (sbyte)123));
TestForType(true, count => CreateFilledData(count, true));
}

[TestCategory("ReadOnlySpanExtensions")]
[TestMethod]
public void Test_ReadOnlySpanExtensions_FilledCount16()
{
TestForType((ushort)4712, count => CreateFilledData(count, (ushort)4712));
TestForType((short)4712, count => CreateFilledData(count, (short)4712));
TestForType((char)4712, count => CreateFilledData(count, (char)4712));
}

[TestCategory("ReadOnlySpanExtensions")]
[TestMethod]
public void Test_ReadOnlySpanExtensions_FilledCount32()
{
TestForType((int)37438941, count => CreateFilledData(count, (int)37438941));
TestForType((uint)37438941, count => CreateFilledData(count, (uint)37438941));
}

[TestCategory("ReadOnlySpanExtensions")]
[TestMethod]
public void Test_ReadOnlySpanExtensions_FilledCount64()
{
TestForType((long)47128480128401, count => CreateFilledData(count, (long)47128480128401));
TestForType((ulong)47128480128401, count => CreateFilledData(count, (ulong)47128480128401));
}

/// <summary>
/// Performs a test for a specified type.
/// </summary>
/// <typeparam name="T">The type to test.</typeparam>
/// <param name="value">The target value to look for.</param>
/// <param name="provider">The function to use to create random data.</param>
private static void TestForType<T>(T value, Func<int, T[]> provider)
where T : unmanaged, IEquatable<T>
{
foreach (var count in TestCounts)
{
T[] data = provider(count);

int result = data.Count(value);
int expected = CountWithForeach(data, value);

Assert.AreEqual(result, expected, $"Failed {typeof(T)} test with count {count}: got {result} instead of {expected}");
}
}

/// <summary>
/// Counts the number of occurrences of a given value into a target <see cref="ReadOnlySpan{T}"/> instance.
/// </summary>
/// <param name="span">The input <see cref="ReadOnlySpan{T}"/> instance to read.</param>
/// <param name="value">The value to look for.</param>
/// <returns>The number of occurrences of <paramref name="value"/> in <paramref name="span"/>.</returns>
[Pure]
private static int CountWithForeach<T>(ReadOnlySpan<T> span, T value)
where T : IEquatable<T>
{
int count = 0;

foreach (var item in span)
{
if (item.Equals(value))
{
count++;
}
}

return count;
}

/// <summary>
/// Creates a random <typeparamref name="T"/> array filled with random data.
/// </summary>
/// <typeparam name="T">The type of items to put in the array.</typeparam>
/// <param name="count">The number of array items to create.</param>
/// <returns>An array of random <typeparamref name="T"/> elements.</returns>
[Pure]
private static T[] CreateRandomData<T>(int count)
where T : unmanaged
{
var random = new Random(count);

T[] data = new T[count];

foreach (ref byte n in MemoryMarshal.AsBytes(data.AsSpan()))
{
n = (byte)random.Next(0, byte.MaxValue);
}

return data;
}

/// <summary>
/// Creates a <typeparamref name="T"/> array filled with a given value.
/// </summary>
/// <typeparam name="T">The type of items to put in the array.</typeparam>
/// <param name="count">The number of array items to create.</param>
/// <param name="value">The value to use to populate the array.</param>
/// <returns>An array of <typeparamref name="T"/> elements.</returns>
[Pure]
private static T[] CreateFilledData<T>(int count, T value)
where T : unmanaged
{
T[] data = new T[count];

data.AsSpan().Fill(value);

return data;
}
}
}
Loading