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

Merge branch 'master' into bugfix/task-result-converter

This commit is contained in:
Alexandre Zollinger Chohfi 2020-08-07 18:05:18 -07:00 committed by GitHub
commit 16f6c4687b
10 changed files with 212 additions and 73 deletions

View File

@ -78,9 +78,9 @@
<!-- NETCORE_RUNTIME: to avoid issues with APIs that assume a specific memory layout, we define a
.NET Core runtime constant to indicate the either .NET Core 2.1 or .NET Core 3.1. These are
runtimes with the same overall memory layout for objects (in particular: strings, SZ arrays
runtimes with the same overall memory layout for objects (in particular: strings, SZ arrays,
and 2D arrays). We can use this constant to make sure that APIs that are exclusively available
for .NET Standard targets do not make any assumtpion of any internals of the runtime being
for .NET Standard targets do not make any assumption of any internals of the runtime being
actually used by the consumers. -->
<DefineConstants>NETSTANDARD2_1_OR_GREATER;SPAN_RUNTIME_SUPPORT;NETCORE_RUNTIME</DefineConstants>
</PropertyGroup>

View File

@ -0,0 +1,82 @@
// 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.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace UnitTests.HighPerformance.Shared.Buffers.Internals
{
/// <summary>
/// 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> : MemoryManager<T>
where T : unmanaged
{
/// <summary>
/// The size of the current instance
/// </summary>
private readonly int length;
/// <summary>
/// The pointer to the underlying <see cref="byte"/> array.
/// </summary>
private IntPtr ptr;
/// <summary>
/// Initializes a new instance of the <see cref="UnmanagedSpanOwner{T}"/> class.
/// </summary>
/// <param name="size">The size of the buffer to rent.</param>
public UnmanagedSpanOwner(int size)
{
this.ptr = Marshal.AllocHGlobal(size * Unsafe.SizeOf<T>());
this.length = size;
}
/// <summary>
/// Gets the length of the buffer in use.
/// </summary>
public int Length => this.length;
/// <summary>
/// Gets a pointer to the start of the buffer in use.
/// </summary>
public T* Ptr => (T*)this.ptr;
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
IntPtr ptr = this.ptr;
if (ptr == IntPtr.Zero)
{
return;
}
this.ptr = IntPtr.Zero;
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

@ -8,6 +8,9 @@
using System.Runtime.InteropServices;
using Microsoft.Toolkit.HighPerformance.Extensions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using UnitTests.HighPerformance.Shared.Buffers.Internals;
#nullable enable
namespace UnitTests.HighPerformance.Extensions
{
@ -168,15 +171,15 @@ public void Test_ReadOnlySpanExtensions_FilledCount64()
/// <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, T[]> provider)
private static void TestForType<T>(T value, Func<int, T, UnmanagedSpanOwner<T>> provider)
where T : unmanaged, IEquatable<T>
{
foreach (var count in TestCounts)
{
T[] data = provider(count, value);
using UnmanagedSpanOwner<T> data = provider(count, value);
int result = data.Count(value);
int expected = CountWithForeach(data, 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}");
}
@ -214,14 +217,14 @@ private static int CountWithForeach<T>(ReadOnlySpan<T> span, T value)
/// <param name="value">The value to look for.</param>
/// <returns>An array of random <typeparamref name="T"/> elements.</returns>
[Pure]
private static T[] CreateRandomData<T>(int count, T value)
private static UnmanagedSpanOwner<T> CreateRandomData<T>(int count, T value)
where T : unmanaged
{
var random = new Random(count);
T[] data = new T[count];
UnmanagedSpanOwner<T> data = new UnmanagedSpanOwner<T>(count);
foreach (ref byte n in MemoryMarshal.AsBytes(data.AsSpan()))
foreach (ref byte n in MemoryMarshal.AsBytes(data.GetSpan()))
{
n = (byte)random.Next(0, byte.MaxValue);
}
@ -229,9 +232,11 @@ private static 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.GetSpan();
for (int i = 0; i < minimum; i++)
{
data[random.Next(0, count)] = value;
span[random.Next(0, count)] = value;
}
return data;
@ -245,12 +250,12 @@ private static T[] CreateRandomData<T>(int count, T value)
/// <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)
private static UnmanagedSpanOwner<T> CreateFilledData<T>(int count, T value)
where T : unmanaged
{
T[] data = new T[count];
UnmanagedSpanOwner<T> data = new UnmanagedSpanOwner<T>(count);
data.AsSpan().Fill(value);
data.GetSpan().Fill(value);
return data;
}

View File

@ -18,7 +18,9 @@ public partial class Test_ReadOnlySpanExtensions
[TestMethod]
public void Test_ReadOnlySpanExtensions_DangerousGetReference()
{
ReadOnlySpan<int> data = CreateRandomData<int>(12, default).AsSpan();
using var owner = CreateRandomData<int>(12, default);
ReadOnlySpan<int> data = owner.GetSpan();
ref int r0 = ref data.DangerousGetReference();
ref int r1 = ref Unsafe.AsRef(data[0]);
@ -30,7 +32,9 @@ public void Test_ReadOnlySpanExtensions_DangerousGetReference()
[TestMethod]
public void Test_ReadOnlySpanExtensions_DangerousGetReferenceAt_Zero()
{
ReadOnlySpan<int> data = CreateRandomData<int>(12, default).AsSpan();
using var owner = CreateRandomData<int>(12, default);
ReadOnlySpan<int> data = owner.GetSpan();
ref int r0 = ref data.DangerousGetReference();
ref int r1 = ref data.DangerousGetReferenceAt(0);
@ -42,7 +46,9 @@ public void Test_ReadOnlySpanExtensions_DangerousGetReferenceAt_Zero()
[TestMethod]
public void Test_ReadOnlySpanExtensions_DangerousGetReferenceAt_Index()
{
ReadOnlySpan<int> data = CreateRandomData<int>(12, default).AsSpan();
using var owner = CreateRandomData<int>(12, default);
ReadOnlySpan<int> data = owner.GetSpan();
ref int r0 = ref data.DangerousGetReferenceAt(5);
ref int r1 = ref Unsafe.AsRef(data[5]);

View File

@ -8,6 +8,7 @@
using System.Runtime.InteropServices;
using Microsoft.Toolkit.HighPerformance.Helpers;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using UnitTests.HighPerformance.Shared.Buffers.Internals;
namespace UnitTests.HighPerformance.Helpers
{
@ -67,19 +68,23 @@ public void Test_HashCodeOfT_VectorUnsupportedTypes_TestRepeat()
[TestMethod]
public void Test_HashCodeOfT_ManagedType_TestRepeat()
{
var localTestCounts = TestCounts.Slice(0, 8);
// Only rent a single array of the maximum necessary size, to save space
string[] data = new string[localTestCounts[localTestCounts.Length - 1]];
var random = new Random();
foreach (var count in TestCounts.Slice(0, 8))
foreach (ref string text in data.AsSpan())
{
string[] data = new string[count];
text = random.NextDouble().ToString("E");
}
foreach (ref string text in data.AsSpan())
{
text = random.NextDouble().ToString("E");
}
foreach (var count in localTestCounts)
{
Span<string> iterationData = data.AsSpan().Slice(0, count);
int hash1 = HashCode<string>.Combine(data);
int hash2 = HashCode<string>.Combine(data);
int hash1 = HashCode<string>.Combine(iterationData);
int hash2 = HashCode<string>.Combine(iterationData);
Assert.AreEqual(hash1, hash2, $"Failed {typeof(string)} test with count {count}: got {hash1} and then {hash2}");
}
@ -95,10 +100,10 @@ private static void TestForType<T>()
{
foreach (var count in TestCounts)
{
T[] data = CreateRandomData<T>(count);
using UnmanagedSpanOwner<T> data = CreateRandomData<T>(count);
int hash1 = HashCode<T>.Combine(data);
int hash2 = HashCode<T>.Combine(data);
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}");
}
@ -111,14 +116,14 @@ private static void TestForType<T>()
/// <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)
private static UnmanagedSpanOwner<T> CreateRandomData<T>(int count)
where T : unmanaged
{
var random = new Random(count);
T[] data = new T[count];
UnmanagedSpanOwner<T> data = new UnmanagedSpanOwner<T>(count);
foreach (ref byte n in MemoryMarshal.AsBytes(data.AsSpan()))
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);
}
}
}

View File

@ -8,6 +8,7 @@
using System.Threading;
using Microsoft.Toolkit.HighPerformance.Helpers;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using UnitTests.HighPerformance.Shared.Buffers.Internals;
namespace UnitTests.HighPerformance.Helpers
{
@ -19,15 +20,15 @@ public unsafe void Test_ParallelHelper_ForEach_In()
{
foreach (int count in TestForCounts)
{
int[] data = CreateRandomData(count);
using UnmanagedSpanOwner<int> data = CreateRandomData(count);
int sum = 0;
ParallelHelper.ForEach<int, Summer>(data.AsMemory(), new Summer(&sum));
ParallelHelper.ForEach<int, Summer>(data.Memory, new Summer(&sum));
int expected = 0;
foreach (int n in data)
foreach (int n in data.GetSpan())
{
expected += n;
}
@ -55,13 +56,13 @@ public unsafe void Test_ParallelHelper_ForEach_In()
/// <param name="count">The number of array items to create.</param>
/// <returns>An array of random <see cref="int"/> elements.</returns>
[Pure]
private static int[] CreateRandomData(int count)
private static UnmanagedSpanOwner<int> CreateRandomData(int count)
{
var random = new Random(count);
int[] data = new int[count];
UnmanagedSpanOwner<int> data = new UnmanagedSpanOwner<int>(count);
foreach (ref int n in data.AsSpan())
foreach (ref int n in data.GetSpan())
{
n = random.Next(0, byte.MaxValue);
}

View File

@ -5,6 +5,7 @@
using System;
using Microsoft.Toolkit.HighPerformance.Helpers;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using UnitTests.HighPerformance.Shared.Buffers.Internals;
namespace UnitTests.HighPerformance.Helpers
{
@ -16,21 +17,26 @@ public void Test_ParallelHelper_ForEach_Ref()
{
foreach (int count in TestForCounts)
{
int[] data = CreateRandomData(count);
int[] copy = data.AsSpan().ToArray();
using UnmanagedSpanOwner<int> data = CreateRandomData(count);
using UnmanagedSpanOwner<int> copy = new UnmanagedSpanOwner<int>(count);
foreach (ref int n in copy.AsSpan())
data.GetSpan().CopyTo(copy.GetSpan());
foreach (ref int n in copy.GetSpan())
{
n = unchecked(n * 397);
}
ParallelHelper.ForEach(data.AsMemory(), new Multiplier(397));
ParallelHelper.ForEach(data.Memory, new Multiplier(397));
Span<int> dataSpan = data.GetSpan();
Span<int> copySpan = copy.GetSpan();
for (int i = 0; i < data.Length; i++)
{
if (data[i] != copy[i])
if (dataSpan[i] != copySpan[i])
{
Assert.Fail($"Item #{i} was not a match, was {data[i]} instead of {copy[i]}");
Assert.Fail($"Item #{i} was not a match, was {dataSpan[i]} instead of {copySpan[i]}");
}
}
}

View File

@ -9,6 +9,7 @@
<Import_RootNamespace>UnitTests.HighPerformance.Shared</Import_RootNamespace>
</PropertyGroup>
<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)Buffers\Internals\UnmanagedSpanOwner.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Buffers\Test_MemoryBufferWriter{T}.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Buffers\Test_ArrayPoolBufferWriter{T}.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Buffers\Test_MemoryOwner{T}.cs" />