Skip to content

Commit 5aa217f

Browse files
authored
Merge branch 'master' into feature/memory-cast
2 parents 259418b + 72d6847 commit 5aa217f

File tree

210 files changed

+14496
-953
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

210 files changed

+14496
-953
lines changed

Microsoft.Toolkit.HighPerformance/Box{T}.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ public static Box<T> GetFrom(object obj)
7878
ThrowInvalidCastExceptionForGetFrom();
7979
}
8080

81-
return Unsafe.As<Box<T>>(obj);
81+
return Unsafe.As<Box<T>>(obj)!;
8282
}
8383

8484
/// <summary>
@@ -94,7 +94,7 @@ public static Box<T> GetFrom(object obj)
9494
[MethodImpl(MethodImplOptions.AggressiveInlining)]
9595
public static Box<T> DangerousGetFrom(object obj)
9696
{
97-
return Unsafe.As<Box<T>>(obj);
97+
return Unsafe.As<Box<T>>(obj)!;
9898
}
9999

100100
/// <summary>
@@ -108,7 +108,7 @@ public static bool TryGetFrom(object obj, [NotNullWhen(true)] out Box<T>? box)
108108
{
109109
if (obj.GetType() == typeof(T))
110110
{
111-
box = Unsafe.As<Box<T>>(obj);
111+
box = Unsafe.As<Box<T>>(obj)!;
112112

113113
return true;
114114
}
@@ -145,7 +145,7 @@ public static implicit operator Box<T>(T value)
145145
// manually be implemented in the Box<T> type. For instance, boxing a float
146146
// and calling ToString() on it directly, on its boxed object or on a Box<T>
147147
// reference retrieved from it will produce the same result in all cases.
148-
return Unsafe.As<Box<T>>(value);
148+
return Unsafe.As<Box<T>>(value)!;
149149
}
150150

151151
/// <inheritdoc/>

Microsoft.Toolkit.HighPerformance/Buffers/ArrayPoolBufferWriter{T}.cs

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using System.Runtime.InteropServices;
1111
using Microsoft.Toolkit.HighPerformance.Buffers.Views;
1212
using Microsoft.Toolkit.HighPerformance.Extensions;
13+
using Microsoft.Toolkit.HighPerformance.Helpers.Internals;
1314

1415
namespace Microsoft.Toolkit.HighPerformance.Buffers
1516
{
@@ -233,15 +234,15 @@ public void Advance(int count)
233234
/// <inheritdoc/>
234235
public Memory<T> GetMemory(int sizeHint = 0)
235236
{
236-
CheckAndResizeBuffer(sizeHint);
237+
CheckBufferAndEnsureCapacity(sizeHint);
237238

238239
return this.array.AsMemory(this.index);
239240
}
240241

241242
/// <inheritdoc/>
242243
public Span<T> GetSpan(int sizeHint = 0)
243244
{
244-
CheckAndResizeBuffer(sizeHint);
245+
CheckBufferAndEnsureCapacity(sizeHint);
245246

246247
return this.array.AsSpan(this.index);
247248
}
@@ -251,9 +252,11 @@ public Span<T> GetSpan(int sizeHint = 0)
251252
/// </summary>
252253
/// <param name="sizeHint">The minimum number of items to ensure space for in <see cref="array"/>.</param>
253254
[MethodImpl(MethodImplOptions.AggressiveInlining)]
254-
private void CheckAndResizeBuffer(int sizeHint)
255+
private void CheckBufferAndEnsureCapacity(int sizeHint)
255256
{
256-
if (this.array is null)
257+
T[]? array = this.array;
258+
259+
if (array is null)
257260
{
258261
ThrowObjectDisposedException();
259262
}
@@ -268,12 +271,32 @@ private void CheckAndResizeBuffer(int sizeHint)
268271
sizeHint = 1;
269272
}
270273

271-
if (sizeHint > FreeCapacity)
274+
if (sizeHint > array!.Length - this.index)
272275
{
273-
int minimumSize = this.index + sizeHint;
276+
ResizeBuffer(sizeHint);
277+
}
278+
}
274279

275-
this.pool.Resize(ref this.array, minimumSize);
280+
/// <summary>
281+
/// Resizes <see cref="array"/> to ensure it can fit the specified number of new items.
282+
/// </summary>
283+
/// <param name="sizeHint">The minimum number of items to ensure space for in <see cref="array"/>.</param>
284+
[MethodImpl(MethodImplOptions.NoInlining)]
285+
private void ResizeBuffer(int sizeHint)
286+
{
287+
int minimumSize = this.index + sizeHint;
288+
289+
// The ArrayPool<T> class has a maximum threshold of 1024 * 1024 for the maximum length of
290+
// pooled arrays, and once this is exceeded it will just allocate a new array every time
291+
// of exactly the requested size. In that case, we manually round up the requested size to
292+
// the nearest power of two, to ensure that repeated consecutive writes when the array in
293+
// use is bigger than that threshold don't end up causing a resize every single time.
294+
if (minimumSize > 1024 * 1024)
295+
{
296+
minimumSize = BitOperations.RoundUpPowerOfTwo(minimumSize);
276297
}
298+
299+
this.pool.Resize(ref this.array, minimumSize);
277300
}
278301

279302
/// <inheritdoc/>

Microsoft.Toolkit.HighPerformance/Buffers/MemoryOwner{T}.cs

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
using System.Diagnostics;
88
using System.Diagnostics.Contracts;
99
using System.Runtime.CompilerServices;
10+
#if NETCORE_RUNTIME
11+
using System.Runtime.InteropServices;
12+
#endif
1013
using Microsoft.Toolkit.HighPerformance.Buffers.Views;
1114
using Microsoft.Toolkit.HighPerformance.Extensions;
1215

@@ -180,7 +183,22 @@ public Span<T> Span
180183
ThrowObjectDisposedException();
181184
}
182185

186+
#if NETCORE_RUNTIME
187+
ref T r0 = ref array!.DangerousGetReferenceAt(this.start);
188+
189+
// On .NET Core runtimes, we can manually create a span from the starting reference to
190+
// skip the argument validations, which include an explicit null check, covariance check
191+
// for the array and the actual validation for the starting offset and target length. We
192+
// only do this on .NET Core as we can leverage the runtime-specific array layout to get
193+
// a fast access to the initial element, which makes this trick worth it. Otherwise, on
194+
// runtimes where we would need to at least access a static field to retrieve the base
195+
// byte offset within an SZ array object, we can get better performance by just using the
196+
// default Span<T> constructor and paying the cost of the extra conditional branches,
197+
// especially if T is a value type, in which case the covariance check is JIT removed.
198+
return MemoryMarshal.CreateSpan(ref r0, this.length);
199+
#else
183200
return new Span<T>(array!, this.start, this.length);
201+
#endif
184202
}
185203
}
186204

@@ -208,6 +226,31 @@ public ref T DangerousGetReference()
208226
return ref array!.DangerousGetReferenceAt(this.start);
209227
}
210228

229+
/// <summary>
230+
/// Gets an <see cref="ArraySegment{T}"/> instance wrapping the underlying <typeparamref name="T"/> array in use.
231+
/// </summary>
232+
/// <returns>An <see cref="ArraySegment{T}"/> instance wrapping the underlying <typeparamref name="T"/> array in use.</returns>
233+
/// <exception cref="ObjectDisposedException">Thrown when the buffer in use has already been disposed.</exception>
234+
/// <remarks>
235+
/// This method is meant to be used when working with APIs that only accept an array as input, and should be used with caution.
236+
/// In particular, the returned array is rented from an array pool, and it is responsibility of the caller to ensure that it's
237+
/// not used after the current <see cref="MemoryOwner{T}"/> instance is disposed. Doing so is considered undefined behavior,
238+
/// as the same array might be in use within another <see cref="MemoryOwner{T}"/> instance.
239+
/// </remarks>
240+
[Pure]
241+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
242+
public ArraySegment<T> DangerousGetArray()
243+
{
244+
T[]? array = this.array;
245+
246+
if (array is null)
247+
{
248+
ThrowObjectDisposedException();
249+
}
250+
251+
return new ArraySegment<T>(array!, this.start, this.length);
252+
}
253+
211254
/// <summary>
212255
/// Slices the buffer currently in use and returns a new <see cref="MemoryOwner{T}"/> instance.
213256
/// </summary>
@@ -222,7 +265,6 @@ public ref T DangerousGetReference()
222265
/// size and copy the previous items into the new one, or needing an additional variable/field
223266
/// to manually handle to track the used range within a given <see cref="MemoryOwner{T}"/> instance.
224267
/// </remarks>
225-
[Pure]
226268
public MemoryOwner<T> Slice(int start, int length)
227269
{
228270
T[]? array = this.array;
@@ -244,6 +286,11 @@ public MemoryOwner<T> Slice(int start, int length)
244286
ThrowInvalidLengthException();
245287
}
246288

289+
// We're transferring the ownership of the underlying array, so the current
290+
// instance no longer needs to be disposed. Because of this, we can manually
291+
// suppress the finalizer to reduce the overhead on the garbage collector.
292+
GC.SuppressFinalize(this);
293+
247294
return new MemoryOwner<T>(start, length, this.pool, array!);
248295
}
249296

Microsoft.Toolkit.HighPerformance/Buffers/SpanOwner{T}.cs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
using System.Diagnostics;
88
using System.Diagnostics.Contracts;
99
using System.Runtime.CompilerServices;
10+
#if NETCORE_RUNTIME
11+
using System.Runtime.InteropServices;
12+
#endif
1013
using Microsoft.Toolkit.HighPerformance.Buffers.Views;
1114
using Microsoft.Toolkit.HighPerformance.Extensions;
1215

@@ -143,7 +146,16 @@ public int Length
143146
public Span<T> Span
144147
{
145148
[MethodImpl(MethodImplOptions.AggressiveInlining)]
146-
get => new Span<T>(this.array, 0, this.length);
149+
get
150+
{
151+
#if NETCORE_RUNTIME
152+
ref T r0 = ref array!.DangerousGetReference();
153+
154+
return MemoryMarshal.CreateSpan(ref r0, this.length);
155+
#else
156+
return new Span<T>(this.array, 0, this.length);
157+
#endif
158+
}
147159
}
148160

149161
/// <summary>
@@ -157,6 +169,23 @@ public ref T DangerousGetReference()
157169
return ref this.array.DangerousGetReference();
158170
}
159171

172+
/// <summary>
173+
/// Gets an <see cref="ArraySegment{T}"/> instance wrapping the underlying <typeparamref name="T"/> array in use.
174+
/// </summary>
175+
/// <returns>An <see cref="ArraySegment{T}"/> instance wrapping the underlying <typeparamref name="T"/> array in use.</returns>
176+
/// <remarks>
177+
/// This method is meant to be used when working with APIs that only accept an array as input, and should be used with caution.
178+
/// In particular, the returned array is rented from an array pool, and it is responsibility of the caller to ensure that it's
179+
/// not used after the current <see cref="SpanOwner{T}"/> instance is disposed. Doing so is considered undefined behavior,
180+
/// as the same array might be in use within another <see cref="SpanOwner{T}"/> instance.
181+
/// </remarks>
182+
[Pure]
183+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
184+
public ArraySegment<T> DangerousGetArray()
185+
{
186+
return new ArraySegment<T>(array!, 0, this.length);
187+
}
188+
160189
/// <summary>
161190
/// Implements the duck-typed <see cref="IDisposable.Dispose"/> method.
162191
/// </summary>

Microsoft.Toolkit.HighPerformance/Buffers/StringPool.cs

Lines changed: 13 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,13 @@
55
using System;
66
using System.Diagnostics.CodeAnalysis;
77
using System.Diagnostics.Contracts;
8-
#if NETCOREAPP3_1
9-
using System.Numerics;
10-
#endif
118
using System.Runtime.CompilerServices;
129
using System.Text;
1310
using Microsoft.Toolkit.HighPerformance.Extensions;
1411
#if !NETSTANDARD1_4
1512
using Microsoft.Toolkit.HighPerformance.Helpers;
1613
#endif
14+
using BitOperations = Microsoft.Toolkit.HighPerformance.Helpers.Internals.BitOperations;
1715

1816
#nullable enable
1917

@@ -79,8 +77,8 @@ static void FindFactors(int size, int factor, out int x, out int y)
7977
a = Math.Sqrt((double)size / factor),
8078
b = factor * a;
8179

82-
x = RoundUpPowerOfTwo((int)a);
83-
y = RoundUpPowerOfTwo((int)b);
80+
x = BitOperations.RoundUpPowerOfTwo((int)a);
81+
y = BitOperations.RoundUpPowerOfTwo((int)b);
8482
}
8583

8684
// We want to find two powers of 2 factors that produce a number
@@ -130,30 +128,6 @@ static void FindFactors(int size, int factor, out int x, out int y)
130128
Size = p2;
131129
}
132130

133-
/// <summary>
134-
/// Rounds up an <see cref="int"/> value to a power of 2.
135-
/// </summary>
136-
/// <param name="x">The input value to round up.</param>
137-
/// <returns>The smallest power of two greater than or equal to <paramref name="x"/>.</returns>
138-
[Pure]
139-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
140-
private static int RoundUpPowerOfTwo(int x)
141-
{
142-
#if NETCOREAPP3_1
143-
return 1 << (32 - BitOperations.LeadingZeroCount((uint)(x - 1)));
144-
#else
145-
x--;
146-
x |= x >> 1;
147-
x |= x >> 2;
148-
x |= x >> 4;
149-
x |= x >> 8;
150-
x |= x >> 16;
151-
x++;
152-
153-
return x;
154-
#endif
155-
}
156-
157131
/// <summary>
158132
/// Gets the shared <see cref="StringPool"/> instance.
159133
/// </summary>
@@ -422,11 +396,11 @@ public object SyncRoot
422396
/// <param name="value">The input <see cref="string"/> instance to cache.</param>
423397
/// <param name="hashcode">The precomputed hashcode for <paramref name="value"/>.</param>
424398
[MethodImpl(MethodImplOptions.AggressiveInlining)]
425-
public unsafe void Add(string value, int hashcode)
399+
public void Add(string value, int hashcode)
426400
{
427401
ref string target = ref TryGet(value.AsSpan(), hashcode);
428402

429-
if (Unsafe.AreSame(ref target, ref Unsafe.AsRef<string>(null)))
403+
if (Unsafe.IsNullRef(ref target))
430404
{
431405
Insert(value, hashcode);
432406
}
@@ -443,11 +417,11 @@ public unsafe void Add(string value, int hashcode)
443417
/// <param name="hashcode">The precomputed hashcode for <paramref name="value"/>.</param>
444418
/// <returns>A <see cref="string"/> instance with the contents of <paramref name="value"/>.</returns>
445419
[MethodImpl(MethodImplOptions.AggressiveInlining)]
446-
public unsafe string GetOrAdd(string value, int hashcode)
420+
public string GetOrAdd(string value, int hashcode)
447421
{
448422
ref string result = ref TryGet(value.AsSpan(), hashcode);
449423

450-
if (!Unsafe.AreSame(ref result, ref Unsafe.AsRef<string>(null)))
424+
if (!Unsafe.IsNullRef(ref result))
451425
{
452426
return result;
453427
}
@@ -464,11 +438,11 @@ public unsafe string GetOrAdd(string value, int hashcode)
464438
/// <param name="hashcode">The precomputed hashcode for <paramref name="span"/>.</param>
465439
/// <returns>A <see cref="string"/> instance with the contents of <paramref name="span"/>, cached if possible.</returns>
466440
[MethodImpl(MethodImplOptions.AggressiveInlining)]
467-
public unsafe string GetOrAdd(ReadOnlySpan<char> span, int hashcode)
441+
public string GetOrAdd(ReadOnlySpan<char> span, int hashcode)
468442
{
469443
ref string result = ref TryGet(span, hashcode);
470444

471-
if (!Unsafe.AreSame(ref result, ref Unsafe.AsRef<string>(null)))
445+
if (!Unsafe.IsNullRef(ref result))
472446
{
473447
return result;
474448
}
@@ -488,11 +462,11 @@ public unsafe string GetOrAdd(ReadOnlySpan<char> span, int hashcode)
488462
/// <param name="value">The resulting cached <see cref="string"/> instance, if present</param>
489463
/// <returns>Whether or not the target <see cref="string"/> instance was found.</returns>
490464
[MethodImpl(MethodImplOptions.AggressiveInlining)]
491-
public unsafe bool TryGet(ReadOnlySpan<char> span, int hashcode, [NotNullWhen(true)] out string? value)
465+
public bool TryGet(ReadOnlySpan<char> span, int hashcode, [NotNullWhen(true)] out string? value)
492466
{
493467
ref string result = ref TryGet(span, hashcode);
494468

495-
if (!Unsafe.AreSame(ref result, ref Unsafe.AsRef<string>(null)))
469+
if (!Unsafe.IsNullRef(ref result))
496470
{
497471
value = result;
498472

@@ -527,7 +501,7 @@ public void Reset()
527501
private unsafe ref string TryGet(ReadOnlySpan<char> span, int hashcode)
528502
{
529503
ref MapEntry mapEntriesRef = ref this.mapEntries.DangerousGetReference();
530-
ref MapEntry entry = ref Unsafe.AsRef<MapEntry>(null);
504+
ref MapEntry entry = ref Unsafe.NullRef<MapEntry>();
531505
int
532506
length = this.buckets.Length,
533507
bucketIndex = hashcode & (length - 1);
@@ -547,7 +521,7 @@ private unsafe ref string TryGet(ReadOnlySpan<char> span, int hashcode)
547521
}
548522
}
549523

550-
return ref Unsafe.AsRef<string>(null);
524+
return ref Unsafe.NullRef<string>();
551525
}
552526

553527
/// <summary>

Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.1D.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public static partial class ArrayExtensions
3131
public static ref T DangerousGetReference<T>(this T[] array)
3232
{
3333
#if NETCORE_RUNTIME
34-
var arrayData = Unsafe.As<RawArrayData>(array);
34+
var arrayData = Unsafe.As<RawArrayData>(array)!;
3535
ref T r0 = ref Unsafe.As<byte, T>(ref arrayData.Data);
3636

3737
return ref r0;
@@ -55,7 +55,7 @@ public static ref T DangerousGetReference<T>(this T[] array)
5555
public static ref T DangerousGetReferenceAt<T>(this T[] array, int i)
5656
{
5757
#if NETCORE_RUNTIME
58-
var arrayData = Unsafe.As<RawArrayData>(array);
58+
var arrayData = Unsafe.As<RawArrayData>(array)!;
5959
ref T r0 = ref Unsafe.As<byte, T>(ref arrayData.Data);
6060
ref T ri = ref Unsafe.Add(ref r0, (nint)(uint)i);
6161

0 commit comments

Comments
 (0)