1
0
mirror of https://github.com/chylex/.NET-Community-Toolkit.git synced 2025-04-13 20:15:45 +02:00

Code refactoring to support Memory<T> tests

This commit is contained in:
Sergio Pedri 2020-08-04 13:46:24 +02:00
parent 9ace1401d5
commit 9dc5481b47
5 changed files with 92 additions and 45 deletions

View File

@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Buffers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@ -12,7 +13,7 @@ namespace UnitTests.HighPerformance.Shared.Buffers.Internals
/// An owner for a buffer of an unmanaged type, recycling <see cref="byte"/> arrays to save memory.
/// </summary>
/// <typeparam name="T">The type of items to store in the rented buffers.</typeparam>
internal sealed unsafe class UnmanagedSpanOwner<T> : IDisposable
internal sealed unsafe class UnmanagedSpanOwner<T> : MemoryManager<T>
where T : unmanaged
{
/// <summary>
@ -45,13 +46,8 @@ public UnmanagedSpanOwner(int size)
/// </summary>
public T* Ptr => (T*)this.ptr;
/// <summary>
/// Gets the <see cref="Memory{T}"/> for the current instance.
/// </summary>
public Span<T> Span => new Span<T>((void*)this.ptr, this.length);
/// <inheritdoc/>
public void Dispose()
protected override void Dispose(bool disposing)
{
IntPtr ptr = this.ptr;
@ -64,5 +60,23 @@ public void Dispose()
Marshal.FreeHGlobal(ptr);
}
/// <inheritdoc/>
public override Span<T> GetSpan()
{
return new Span<T>((void*)this.ptr, this.length);
}
/// <inheritdoc/>
public override MemoryHandle Pin(int elementIndex = 0)
{
throw new NotImplementedException();
}
/// <inheritdoc/>
public override void Unpin()
{
throw new NotImplementedException();
}
}
}

View File

@ -178,8 +178,8 @@ private static void TestForType<T>(T value, Func<int, T, UnmanagedSpanOwner<T>>
{
using UnmanagedSpanOwner<T> data = provider(count, value);
int result = data.Span.Count(value);
int expected = CountWithForeach(data.Span, value);
int result = data.GetSpan().Count(value);
int expected = CountWithForeach(data.GetSpan(), value);
Assert.AreEqual(result, expected, $"Failed {typeof(T)} test with count {count}: got {result} instead of {expected}");
}
@ -224,7 +224,7 @@ private static UnmanagedSpanOwner<T> CreateRandomData<T>(int count, T value)
UnmanagedSpanOwner<T> data = new UnmanagedSpanOwner<T>(count);
foreach (ref byte n in MemoryMarshal.AsBytes(data.Span))
foreach (ref byte n in MemoryMarshal.AsBytes(data.GetSpan()))
{
n = (byte)random.Next(0, byte.MaxValue);
}
@ -232,7 +232,7 @@ private static UnmanagedSpanOwner<T> CreateRandomData<T>(int count, T value)
// Fill at least 20% of the items with a matching value
int minimum = count / 20;
Span<T> span = data.Span;
Span<T> span = data.GetSpan();
for (int i = 0; i < minimum; i++)
{
@ -255,7 +255,7 @@ private static UnmanagedSpanOwner<T> CreateFilledData<T>(int count, T value)
{
UnmanagedSpanOwner<T> data = new UnmanagedSpanOwner<T>(count);
data.Span.Fill(value);
data.GetSpan().Fill(value);
return data;
}

View File

@ -102,8 +102,8 @@ private static void TestForType<T>()
{
using UnmanagedSpanOwner<T> data = CreateRandomData<T>(count);
int hash1 = HashCode<T>.Combine(data.Span);
int hash2 = HashCode<T>.Combine(data.Span);
int hash1 = HashCode<T>.Combine(data.GetSpan());
int hash2 = HashCode<T>.Combine(data.GetSpan());
Assert.AreEqual(hash1, hash2, $"Failed {typeof(T)} test with count {count}: got {hash1} and then {hash2}");
}
@ -123,7 +123,7 @@ private static UnmanagedSpanOwner<T> CreateRandomData<T>(int count)
UnmanagedSpanOwner<T> data = new UnmanagedSpanOwner<T>(count);
foreach (ref byte n in MemoryMarshal.AsBytes(data.Span))
foreach (ref byte n in MemoryMarshal.AsBytes(data.GetSpan()))
{
n = (byte)random.Next(0, byte.MaxValue);
}

View File

@ -6,6 +6,7 @@
using Microsoft.Toolkit.HighPerformance.Extensions;
using Microsoft.Toolkit.HighPerformance.Helpers;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using UnitTests.HighPerformance.Shared.Buffers.Internals;
namespace UnitTests.HighPerformance.Helpers
{
@ -19,15 +20,17 @@ public partial class Test_ParallelHelper
[TestCategory("ParallelHelper")]
[TestMethod]
public void Test_ParallelHelper_ForWithIndices()
public unsafe void Test_ParallelHelper_ForWithIndices()
{
foreach (int count in TestForCounts)
{
int[] data = new int[count];
using UnmanagedSpanOwner<int> data = new UnmanagedSpanOwner<int>(count);
ParallelHelper.For(0, data.Length, new Assigner(data));
data.GetSpan().Clear();
foreach (var item in data.Enumerate())
ParallelHelper.For(0, data.Length, new Assigner(data.Length, data.Ptr));
foreach (var item in data.GetSpan().Enumerate())
{
if (item.Index != item.Value)
{
@ -56,15 +59,17 @@ public void Test_ParallelHelper_ForInvalidRange_RangeAll()
[TestCategory("ParallelHelper")]
[TestMethod]
public void Test_ParallelHelper_ForWithRanges()
public unsafe void Test_ParallelHelper_ForWithRanges()
{
foreach (int count in TestForCounts)
{
int[] data = new int[count];
using UnmanagedSpanOwner<int> data = new UnmanagedSpanOwner<int>(count);
ParallelHelper.For(..data.Length, new Assigner(data));
data.GetSpan().Clear();
foreach (var item in data.Enumerate())
ParallelHelper.For(..data.Length, new Assigner(data.Length, data.Ptr));
foreach (var item in data.GetSpan().Enumerate())
{
if (item.Index != item.Value)
{
@ -78,21 +83,31 @@ public void Test_ParallelHelper_ForWithRanges()
/// <summary>
/// A type implementing <see cref="IAction"/> to initialize an array
/// </summary>
private readonly struct Assigner : IAction
private readonly unsafe struct Assigner : IAction
{
private readonly int[] array;
private readonly int length;
private readonly int* ptr;
public Assigner(int[] array) => this.array = array;
public Assigner(int length, int* ptr)
{
this.length = length;
this.ptr = ptr;
}
/// <inheritdoc/>
public void Invoke(int i)
{
if (this.array[i] != 0)
if ((uint)i >= (uint)this.length)
{
throw new InvalidOperationException($"Invalid target position {i}, was {this.array[i]} instead of 0");
throw new IndexOutOfRangeException($"The target position was out of range, was {i} and should've been in [0, {this.length})");
}
this.array[i] = i;
if (this.ptr[i] != 0)
{
throw new InvalidOperationException($"Invalid target position {i}, was {this.ptr[i]} instead of 0");
}
this.ptr[i] = i;
}
}
}

View File

@ -6,6 +6,7 @@
using System.Drawing;
using Microsoft.Toolkit.HighPerformance.Helpers;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using UnitTests.HighPerformance.Shared.Buffers.Internals;
namespace UnitTests.HighPerformance.Helpers
{
@ -27,21 +28,23 @@ public partial class Test_ParallelHelper
[TestCategory("ParallelHelper")]
[TestMethod]
public void Test_ParallelHelper_For2DWithIndices()
public unsafe void Test_ParallelHelper_For2DWithIndices()
{
foreach (var size in TestFor2DSizes)
{
int[,] data = new int[size.Height, size.Width];
using UnmanagedSpanOwner<int> data = new UnmanagedSpanOwner<int>(size.Height * size.Width);
ParallelHelper.For2D(0, size.Height, 0, size.Width, new Assigner2D(data));
data.GetSpan().Clear();
ParallelHelper.For2D(0, size.Height, 0, size.Width, new Assigner2D(size.Height, size.Width, data.Ptr));
for (int i = 0; i < size.Height; i++)
{
for (int j = 0; j < size.Width; j++)
{
if (data[i, j] != unchecked(i * 397 ^ j))
if (data.Ptr[(i * size.Width) + j] != unchecked(i * 397 ^ j))
{
Assert.Fail($"Invalid item at position [{i},{j}], value was {data[i, j]} instead of {unchecked(i * 397 ^ j)}");
Assert.Fail($"Invalid item at position [{i},{j}], value was {data.Ptr[(i * size.Width) + j]} instead of {unchecked(i * 397 ^ j)}");
}
}
}
@ -67,21 +70,23 @@ public void Test_ParallelHelper_For2DInvalidRange_RangeAll()
[TestCategory("ParallelHelper")]
[TestMethod]
public void Test_ParallelHelper_For2DWithRanges()
public unsafe void Test_ParallelHelper_For2DWithRanges()
{
foreach (var size in TestFor2DSizes)
{
int[,] data = new int[size.Height, size.Width];
using UnmanagedSpanOwner<int> data = new UnmanagedSpanOwner<int>(size.Height * size.Width);
ParallelHelper.For2D(..size.Height, ..size.Width, new Assigner2D(data));
data.GetSpan().Clear();
ParallelHelper.For2D(..size.Height, ..size.Width, new Assigner2D(size.Height, size.Width, data.Ptr));
for (int i = 0; i < size.Height; i++)
{
for (int j = 0; j < size.Width; j++)
{
if (data[i, j] != unchecked(i * 397 ^ j))
if (data.Ptr[(i * size.Width) + j] != unchecked(i * 397 ^ j))
{
Assert.Fail($"Invalid item at position [{i},{j}], value was {data[i, j]} instead of {unchecked(i * 397 ^ j)}");
Assert.Fail($"Invalid item at position [{i},{j}], value was {data.Ptr[(i * size.Width) + j]} instead of {unchecked(i * 397 ^ j)}");
}
}
}
@ -92,21 +97,34 @@ public void Test_ParallelHelper_For2DWithRanges()
/// <summary>
/// A type implementing <see cref="IAction"/> to initialize a 2D array
/// </summary>
private readonly struct Assigner2D : IAction2D
private readonly unsafe struct Assigner2D : IAction2D
{
private readonly int[,] array;
private readonly int height;
private readonly int width;
private readonly int* ptr;
public Assigner2D(int[,] array) => this.array = array;
public Assigner2D(int height, int width, int* ptr)
{
this.height = height;
this.width = width;
this.ptr = ptr;
}
/// <inheritdoc/>
public void Invoke(int i, int j)
{
if (this.array[i, j] != 0)
if ((uint)i >= (uint)this.height ||
(uint)j >= (uint)this.width)
{
throw new InvalidOperationException($"Invalid target position [{i},{j}], was {this.array[i, j]} instead of 0");
throw new IndexOutOfRangeException($"The target position was invalid, was [{i}, {j}], should've been in [0, {this.height}] and [0, {this.width}]");
}
this.array[i, j] = unchecked(i * 397 ^ j);
if (this.ptr[(i * this.width) + j] != 0)
{
throw new InvalidOperationException($"Invalid target position [{i},{j}], was {this.ptr[(i * this.width) + j]} instead of 0");
}
this.ptr[(i * this.width) + j] = unchecked(i * 397 ^ j);
}
}
}