Skip to content

Commit 52da409

Browse files
committed
Reduced memory usage in Count tests
1 parent a294b85 commit 52da409

File tree

2 files changed

+74
-14
lines changed

2 files changed

+74
-14
lines changed

UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ReadOnlySpanExtensions.Count.cs

Lines changed: 65 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,16 @@
33
// See the LICENSE file in the project root for more information.
44

55
using System;
6+
using System.Buffers;
67
using System.Diagnostics.CodeAnalysis;
78
using System.Diagnostics.Contracts;
9+
using System.Runtime.CompilerServices;
810
using System.Runtime.InteropServices;
911
using Microsoft.Toolkit.HighPerformance.Extensions;
1012
using Microsoft.VisualStudio.TestTools.UnitTesting;
1113

14+
#nullable enable
15+
1216
namespace UnitTests.HighPerformance.Extensions
1317
{
1418
public partial class Test_ReadOnlySpanExtensions
@@ -168,15 +172,15 @@ public void Test_ReadOnlySpanExtensions_FilledCount64()
168172
/// <typeparam name="T">The type to test.</typeparam>
169173
/// <param name="value">The target value to look for.</param>
170174
/// <param name="provider">The function to use to create random data.</param>
171-
private static void TestForType<T>(T value, Func<int, T, T[]> provider)
175+
private static void TestForType<T>(T value, Func<int, T, UnmanagedSpanOwner<T>> provider)
172176
where T : unmanaged, IEquatable<T>
173177
{
174178
foreach (var count in TestCounts)
175179
{
176-
T[] data = provider(count, value);
180+
using UnmanagedSpanOwner<T> data = provider(count, value);
177181

178-
int result = data.Count(value);
179-
int expected = CountWithForeach(data, value);
182+
int result = data.Span.Count(value);
183+
int expected = CountWithForeach(data.Span, value);
180184

181185
Assert.AreEqual(result, expected, $"Failed {typeof(T)} test with count {count}: got {result} instead of {expected}");
182186
}
@@ -214,24 +218,26 @@ private static int CountWithForeach<T>(ReadOnlySpan<T> span, T value)
214218
/// <param name="value">The value to look for.</param>
215219
/// <returns>An array of random <typeparamref name="T"/> elements.</returns>
216220
[Pure]
217-
private static T[] CreateRandomData<T>(int count, T value)
221+
private static UnmanagedSpanOwner<T> CreateRandomData<T>(int count, T value)
218222
where T : unmanaged
219223
{
220224
var random = new Random(count);
221225

222-
T[] data = new T[count];
226+
UnmanagedSpanOwner<T> data = new UnmanagedSpanOwner<T>(count);
223227

224-
foreach (ref byte n in MemoryMarshal.AsBytes(data.AsSpan()))
228+
foreach (ref byte n in MemoryMarshal.AsBytes(data.Span))
225229
{
226230
n = (byte)random.Next(0, byte.MaxValue);
227231
}
228232

229233
// Fill at least 20% of the items with a matching value
230234
int minimum = count / 20;
231235

236+
Span<T> span = data.Span;
237+
232238
for (int i = 0; i < minimum; i++)
233239
{
234-
data[random.Next(0, count)] = value;
240+
span[random.Next(0, count)] = value;
235241
}
236242

237243
return data;
@@ -245,14 +251,62 @@ private static T[] CreateRandomData<T>(int count, T value)
245251
/// <param name="value">The value to use to populate the array.</param>
246252
/// <returns>An array of <typeparamref name="T"/> elements.</returns>
247253
[Pure]
248-
private static T[] CreateFilledData<T>(int count, T value)
254+
private static UnmanagedSpanOwner<T> CreateFilledData<T>(int count, T value)
249255
where T : unmanaged
250256
{
251-
T[] data = new T[count];
257+
UnmanagedSpanOwner<T> data = new UnmanagedSpanOwner<T>(count);
252258

253-
data.AsSpan().Fill(value);
259+
data.Span.Fill(value);
254260

255261
return data;
256262
}
263+
264+
/// <summary>
265+
/// An owner for a buffer of an unmanaged type, recycling <see cref="byte"/> arrays to save memory.
266+
/// </summary>
267+
/// <typeparam name="T">The type of items to store in the rented buffers.</typeparam>
268+
private sealed class UnmanagedSpanOwner<T> : IDisposable
269+
where T : unmanaged
270+
{
271+
/// <summary>
272+
/// The size of the current instance
273+
/// </summary>
274+
private readonly int length;
275+
276+
/// <summary>
277+
/// The underlying <see cref="byte"/> array.
278+
/// </summary>
279+
private byte[]? array;
280+
281+
/// <summary>
282+
/// Initializes a new instance of the <see cref="UnmanagedSpanOwner{T}"/> class.
283+
/// </summary>
284+
/// <param name="size">The size of the buffer to rent.</param>
285+
public UnmanagedSpanOwner(int size)
286+
{
287+
this.array = ArrayPool<byte>.Shared.Rent(size * Unsafe.SizeOf<T>());
288+
this.length = size;
289+
}
290+
291+
/// <inheritdoc/>
292+
public void Dispose()
293+
{
294+
byte[]? array = this.array;
295+
296+
if (array is null)
297+
{
298+
return;
299+
}
300+
301+
this.array = null;
302+
303+
ArrayPool<byte>.Shared.Return(array);
304+
}
305+
306+
/// <summary>
307+
/// Gets the <see cref="Memory{T}"/> for the current instance.
308+
/// </summary>
309+
public Span<T> Span => this.array.AsSpan().Cast<byte, T>().Slice(0, this.length);
310+
}
257311
}
258312
}

UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_ReadOnlySpanExtensions.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ public partial class Test_ReadOnlySpanExtensions
1818
[TestMethod]
1919
public void Test_ReadOnlySpanExtensions_DangerousGetReference()
2020
{
21-
ReadOnlySpan<int> data = CreateRandomData<int>(12, default).AsSpan();
21+
using var owner = CreateRandomData<int>(12, default);
22+
23+
ReadOnlySpan<int> data = owner.Span;
2224

2325
ref int r0 = ref data.DangerousGetReference();
2426
ref int r1 = ref Unsafe.AsRef(data[0]);
@@ -30,7 +32,9 @@ public void Test_ReadOnlySpanExtensions_DangerousGetReference()
3032
[TestMethod]
3133
public void Test_ReadOnlySpanExtensions_DangerousGetReferenceAt_Zero()
3234
{
33-
ReadOnlySpan<int> data = CreateRandomData<int>(12, default).AsSpan();
35+
using var owner = CreateRandomData<int>(12, default);
36+
37+
ReadOnlySpan<int> data = owner.Span;
3438

3539
ref int r0 = ref data.DangerousGetReference();
3640
ref int r1 = ref data.DangerousGetReferenceAt(0);
@@ -42,7 +46,9 @@ public void Test_ReadOnlySpanExtensions_DangerousGetReferenceAt_Zero()
4246
[TestMethod]
4347
public void Test_ReadOnlySpanExtensions_DangerousGetReferenceAt_Index()
4448
{
45-
ReadOnlySpan<int> data = CreateRandomData<int>(12, default).AsSpan();
49+
using var owner = CreateRandomData<int>(12, default);
50+
51+
ReadOnlySpan<int> data = owner.Span;
4652

4753
ref int r0 = ref data.DangerousGetReferenceAt(5);
4854
ref int r1 = ref Unsafe.AsRef(data[5]);

0 commit comments

Comments
 (0)