Microsoft.Toolkit.HighPerformance package (part 2) (#3273)
* Added support for .NET Core 2.1 and 3.1 targets * Fixed T[] dangerous accessors on .NET Standard * Minor speed improvements to Array2DColumnEnumerable<T> * Updated 2D array extensions * Minor code tweaks * Added DangerousGetLookupReferenceAt<T> extension * Added BitHelper.HasLookupFlag APIs * Added BitHelper.ExtractRange and SetRange APIs * Added bool bitwise mask extensions * Added StreamExtensions class on .NET Standard 2.0 * Added IBufferWriterExtensions type * Added more Stream extensions * Added object field offset extensions * Minor code refactoring * Improved description * Switched managed count to IntPtr * Refactored GetDjb2HashCode code, minor tweaks * Unit tests fixes * Refactored Count extensions, minor tweaks * Fixed incorrect operators precedence * Added SpanHelper.GetDjb2LikeByteHash method * Minor code refactoring * Code refactoring to HashCode<T> * Minor style tweaks * Updated comments styles to follow style convention * Minor code tweaks to the Count method * More code tweaks to the Count method * Added tests for DangerousGetObjectDataByteOffset * Fixed visibility of new object extensions * Added tests for 64-bit extract/set range APIs * Added tests for bool to bitwise mask APIs * Fixed MemoryStream EOF exception type * Enabled Stream extension tests on UWP * Added StreamExtensions read/write value tests * Minor code refactoring to IBufferWriter<T> extensions * Added IBufferWriterExtensions tests * Added MemoryBufferWriter<T> type * Added IBuffer<T> interface * Added MemoryBufferWriterDebugView<T> type * Added MemoryBufferWriter<T> tests * Fixed a unit test * Fixed incorrect inlining attribute * Removed leftover partial modifier * Added missing readonly modifier for lock structs * Improved summary for the Box<T> type * Added comments for #pragma warning disable items * Added info on (x << 5) + x over x * 33 optimization * Improved XML docs for BitHelper APIs * Fixed potentially misleading test method names * Added comment for MemoryOwner<T>.Dispose test * Fixed multi-target support for string extensions * Improved IMemoryOwner.AsStream tests * Added XML remarks to the HashCode<T> type * Improved docs for ReadOnlySpanExtensions.GetDjb2HashCode<T> * Tweaked an XML link * Tweaked attributes for SpanHelper methods * Minor optimizations to SpanHelper.Count * Fixed an overflow bug in SpanHelper.Count * Speed improvements to SpanHelper.Count ~35% speedup with large collections of byte-size values, ~10% with other types * Updated .csproj description * Added float/double support to SpanHelper.Count * Added exception to the Box<T> constructor * Removed unnecessary [SuppressMessage] attributes * Removed [SuppressMessage] attributes * Changed NullableRef<T>-s exceptions to follow Nullable<T> * Added more operators to NullableRef<T> types * Added missing in modifiers in ParallelHelper.For overloads * Removed SIMD support for float/double count It lacked proper handling of NaN values and both (signed) zeros * Fixed compiler directives in SpanEnumerable<T> * Updated ReadOnlySpanEnumerable<T> type * Updated NuGet package tags * Added HighPerformance package to readme.md list * Added .NET Standard 1.4 target * Added tests for implicit NullableRef<T> operators * Removed unnecessary using directive * Added missing sealed modifiers to MemoryStream methods * Added Span<T>.IndexOf(ref T) extension * Added ReadOnlySpan<T>.IndexOf<T>(in T) extension * Added missing changes (incorrect commit) * Removed unnecessary workaround (method is inlined) * Added improved remarks for bool.ToBitwiseMask32/64 * Added internal NotNullWhen attribute when < .NET Standard 2.1 * Enabled .csproj-wide nullability annotations * Minor code style tweaks * Added [ReadOnly]NullableRef<T>.Null static properties
This commit is contained in:
Родитель
b24953ac8a
Коммит
059cf83f1f
|
@ -237,6 +237,11 @@ dotnet_naming_style.prefix_private_field_with_underscore.capitalization
|
|||
dotnet_naming_style.prefix_private_field_with_underscore.required_prefix = _
|
||||
|
||||
# Code files
|
||||
|
||||
# SA1009: Closing parenthesis should be spaced correctly
|
||||
# Needed for null forgiving operator after functions, "foo()!"
|
||||
dotnet_diagnostic.SA1009.severity = none
|
||||
|
||||
[*.{cs,vb}]
|
||||
|
||||
# Migrate back from old Toolkit.ruleset
|
||||
|
|
|
@ -86,7 +86,7 @@
|
|||
<UseVSHostingProcess>false</UseVSHostingProcess>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
<UseDotNetNativeToolchain>true</UseDotNetNativeToolchain>
|
||||
<UseDotNetNativeToolchain>true</UseDotNetNativeToolchain>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
// 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.
|
||||
|
||||
#if !NETSTANDARD2_1_OR_GREATER
|
||||
|
||||
namespace System.Diagnostics.CodeAnalysis
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies that when a method returns <see cref="ReturnValue"/>, the parameter
|
||||
/// will not be null even if the corresponding type allows it.
|
||||
/// </summary>
|
||||
/// <remarks>Internal copy of the .NET Standard 2.1 attribute.</remarks>
|
||||
[AttributeUsage(AttributeTargets.Parameter)]
|
||||
internal sealed class NotNullWhenAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NotNullWhenAttribute"/> class.
|
||||
/// </summary>
|
||||
/// <param name="returnValue">
|
||||
/// The return value condition. If the method returns this value,
|
||||
/// the associated parameter will not be <see langword="null"/>.
|
||||
/// </param>
|
||||
public NotNullWhenAttribute(bool returnValue)
|
||||
{
|
||||
ReturnValue = returnValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the return value should be <see langword="true"/>.
|
||||
/// </summary>
|
||||
public bool ReturnValue { get; }
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -8,12 +8,31 @@ using System.Diagnostics.CodeAnalysis;
|
|||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see langword="class"/> that represents a boxed <typeparamref name="T"/> value on the managed heap.
|
||||
/// This is a "shadow" type that can be used in place of a non-generic <see cref="object"/> reference to a
|
||||
/// boxed value type, to make the code more expressive and reduce the chances of errors.
|
||||
/// Consider this example:
|
||||
/// <code>
|
||||
/// object obj = 42;
|
||||
///
|
||||
/// // Manual, error prone unboxing
|
||||
/// int sum = (int)obj + 1;
|
||||
/// </code>
|
||||
/// In this example, it is not possible to know in advance what type is actually being boxed in a given
|
||||
/// <see cref="object"/> instance, making the code less robust at build time. The <see cref="Box{T}"/>
|
||||
/// type can be used as a drop-in replacement in this case, like so:
|
||||
/// <code>
|
||||
/// Box<int> box = 42;
|
||||
///
|
||||
/// // Build-time validation, automatic unboxing
|
||||
/// int sum = box.Value + 1;
|
||||
/// </code>
|
||||
/// This type can also be useful when dealing with large custom value types that are also boxed, as
|
||||
/// it allows to retrieve a mutable reference to the boxing value. This means that a given boxed
|
||||
/// value can be mutated in-place, instead of having to allocate a new updated boxed instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of value being boxed.</typeparam>
|
||||
[DebuggerDisplay("{ToString(),raw}")]
|
||||
|
@ -39,8 +58,10 @@ namespace Microsoft.Toolkit.HighPerformance
|
|||
/// This constructor is never used, it is only declared in order to mark it with
|
||||
/// the <see langword="private"/> visibility modifier and prevent direct use.
|
||||
/// </remarks>
|
||||
/// <exception cref="InvalidOperationException">Always thrown when this constructor is used (eg. from reflection).</exception>
|
||||
private Box()
|
||||
{
|
||||
throw new InvalidOperationException("The Microsoft.Toolkit.HighPerformance.Box<T> constructor should never be used");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -83,18 +104,7 @@ namespace Microsoft.Toolkit.HighPerformance
|
|||
/// <param name="box">The resulting <see cref="Box{T}"/> reference, if <paramref name="obj"/> was a boxed <typeparamref name="T"/> value.</param>
|
||||
/// <returns><see langword="true"/> if a <see cref="Box{T}"/> instance was retrieved correctly, <see langword="false"/> otherwise.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1115", Justification = "Comment for [NotNullWhen] attribute")]
|
||||
public static bool TryGetFrom(
|
||||
object obj,
|
||||
#if NETSTANDARD2_1
|
||||
/* On .NET Standard 2.1, we can add the [NotNullWhen] attribute
|
||||
* to let the code analysis engine know that whenever this method
|
||||
* returns true, box will always be assigned to a non-null value.
|
||||
* This will eliminate the null warnings when in a branch that
|
||||
* is only taken when this method returns true. */
|
||||
[NotNullWhen(true)]
|
||||
#endif
|
||||
out Box<T>? box)
|
||||
public static bool TryGetFrom(object obj, [NotNullWhen(true)] out Box<T>? box)
|
||||
{
|
||||
if (obj.GetType() == typeof(T))
|
||||
{
|
||||
|
@ -125,38 +135,38 @@ namespace Microsoft.Toolkit.HighPerformance
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static implicit operator Box<T>(T value)
|
||||
{
|
||||
/* The Box<T> type is never actually instantiated.
|
||||
* Here we are just boxing the input T value, and then reinterpreting
|
||||
* that object reference as a Box<T> reference. As such, the Box<T>
|
||||
* type is really only used as an interface to access the contents
|
||||
* of a boxed value type. This also makes it so that additional methods
|
||||
* like ToString() or GetHashCode() will automatically be referenced from
|
||||
* the method table of the boxed object, meaning that they don't need to
|
||||
* manually be implemented in the Box<T> type. For instance, boxing a float
|
||||
* and calling ToString() on it directly, on its boxed object or on a Box<T>
|
||||
* reference retrieved from it will produce the same result in all cases. */
|
||||
// The Box<T> type is never actually instantiated.
|
||||
// Here we are just boxing the input T value, and then reinterpreting
|
||||
// that object reference as a Box<T> reference. As such, the Box<T>
|
||||
// type is really only used as an interface to access the contents
|
||||
// of a boxed value type. This also makes it so that additional methods
|
||||
// like ToString() or GetHashCode() will automatically be referenced from
|
||||
// the method table of the boxed object, meaning that they don't need to
|
||||
// manually be implemented in the Box<T> type. For instance, boxing a float
|
||||
// and calling ToString() on it directly, on its boxed object or on a Box<T>
|
||||
// reference retrieved from it will produce the same result in all cases.
|
||||
return Unsafe.As<Box<T>>(value);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
/* Here we're overriding the base object virtual methods to ensure
|
||||
* calls to those methods have a correct results on all runtimes.
|
||||
* For instance, not doing so is causing issue on .NET Core 2.1 Release
|
||||
* due to how the runtime handles the Box<T> reference to an actual
|
||||
* boxed T value (not a concrete Box<T> instance as it would expect).
|
||||
* To fix that, the overrides will simply call the expected methods
|
||||
* directly on the boxed T values. These methods will be directly
|
||||
* invoked by the JIT compiler when using a Box<T> reference. When
|
||||
* an object reference is used instead, the call would be forwarded
|
||||
* to those same methods anyway, since the method table for an object
|
||||
* representing a T instance is the one of type T anyway. */
|
||||
return this.GetReference().ToString();
|
||||
// Here we're overriding the base object virtual methods to ensure
|
||||
// calls to those methods have a correct results on all runtimes.
|
||||
// For instance, not doing so is causing issue on .NET Core 2.1 Release
|
||||
// due to how the runtime handles the Box<T> reference to an actual
|
||||
// boxed T value (not a concrete Box<T> instance as it would expect).
|
||||
// To fix that, the overrides will simply call the expected methods
|
||||
// directly on the boxed T values. These methods will be directly
|
||||
// invoked by the JIT compiler when using a Box<T> reference. When
|
||||
// an object reference is used instead, the call would be forwarded
|
||||
// to those same methods anyway, since the method table for an object
|
||||
// representing a T instance is the one of type T anyway.
|
||||
return this.GetReference().ToString()!;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals(object obj)
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return Equals(this, obj);
|
||||
}
|
||||
|
@ -177,11 +187,12 @@ namespace Microsoft.Toolkit.HighPerformance
|
|||
}
|
||||
}
|
||||
|
||||
#pragma warning disable SA1402 // Extensions being declared after the type they apply to
|
||||
#pragma warning disable SA1204 // Extension class to replace instance methods for Box<T>
|
||||
|
||||
/// <summary>
|
||||
/// Helpers for working with the <see cref="Box{T}"/> type.
|
||||
/// </summary>
|
||||
[SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402", Justification = "Extension class to replace instance methods for Box<T>")]
|
||||
[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1204", Justification = "Extensions being declared after the type they apply to")]
|
||||
public static class BoxExtensions
|
||||
{
|
||||
/// <summary>
|
||||
|
@ -195,24 +206,24 @@ namespace Microsoft.Toolkit.HighPerformance
|
|||
public static ref T GetReference<T>(this Box<T> box)
|
||||
where T : struct
|
||||
{
|
||||
/* The reason why this method is an extension and is not part of
|
||||
* the Box<T> type itself is that Box<T> is really just a mask
|
||||
* used over object references, but it is never actually instantiated.
|
||||
* Because of this, the method table of the objects in the heap will
|
||||
* be the one of type T created by the runtime, and not the one of
|
||||
* the Box<T> type. To avoid potential issues when invoking this method
|
||||
* on different runtimes, which might handle that scenario differently,
|
||||
* we use an extension method, which is just syntactic sugar for a static
|
||||
* method belonging to another class. This isn't technically necessary,
|
||||
* but it's just an extra precaution since the syntax for users remains
|
||||
* exactly the same anyway. Here we just call the Unsafe.Unbox<T>(object)
|
||||
* API, which is hidden away for users of the type for simplicity.
|
||||
* Note that this API will always actually involve a conditional
|
||||
* branch, which is introduced by the JIT compiler to validate the
|
||||
* object instance being unboxed. But since the alternative of
|
||||
* manually tracking the offset to the boxed data would be both
|
||||
* more error prone, and it would still introduce some overhead,
|
||||
* this doesn't really matter in this case anyway. */
|
||||
// The reason why this method is an extension and is not part of
|
||||
// the Box<T> type itself is that Box<T> is really just a mask
|
||||
// used over object references, but it is never actually instantiated.
|
||||
// Because of this, the method table of the objects in the heap will
|
||||
// be the one of type T created by the runtime, and not the one of
|
||||
// the Box<T> type. To avoid potential issues when invoking this method
|
||||
// on different runtimes, which might handle that scenario differently,
|
||||
// we use an extension method, which is just syntactic sugar for a static
|
||||
// method belonging to another class. This isn't technically necessary,
|
||||
// but it's just an extra precaution since the syntax for users remains
|
||||
// exactly the same anyway. Here we just call the Unsafe.Unbox<T>(object)
|
||||
// API, which is hidden away for users of the type for simplicity.
|
||||
// Note that this API will always actually involve a conditional
|
||||
// branch, which is introduced by the JIT compiler to validate the
|
||||
// object instance being unboxed. But since the alternative of
|
||||
// manually tracking the offset to the boxed data would be both
|
||||
// more error prone, and it would still introduce some overhead,
|
||||
// this doesn't really matter in this case anyway.
|
||||
return ref Unsafe.Unbox<T>(box);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,15 +5,12 @@
|
|||
using System;
|
||||
using System.Buffers;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Toolkit.HighPerformance.Buffers.Views;
|
||||
using Microsoft.Toolkit.HighPerformance.Extensions;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Buffers
|
||||
{
|
||||
/// <summary>
|
||||
|
@ -29,7 +26,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
|
|||
/// </remarks>
|
||||
[DebuggerTypeProxy(typeof(ArrayPoolBufferWriterDebugView<>))]
|
||||
[DebuggerDisplay("{ToString(),raw}")]
|
||||
public sealed class ArrayPoolBufferWriter<T> : IBufferWriter<T>, IMemoryOwner<T>
|
||||
public sealed class ArrayPoolBufferWriter<T> : IBuffer<T>, IMemoryOwner<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// The default buffer size to use to expand empty arrays.
|
||||
|
@ -41,7 +38,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
|
|||
/// </summary>
|
||||
private T[]? array;
|
||||
|
||||
#pragma warning disable IDE0032
|
||||
#pragma warning disable IDE0032 // Use field over auto-property (clearer and faster)
|
||||
/// <summary>
|
||||
/// The starting offset within <see cref="array"/>.
|
||||
/// </summary>
|
||||
|
@ -53,9 +50,9 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
|
|||
/// </summary>
|
||||
public ArrayPoolBufferWriter()
|
||||
{
|
||||
/* Since we're using pooled arrays, we can rent the buffer with the
|
||||
* default size immediately, we don't need to use lazy initialization
|
||||
* to save unnecessary memory allocations in this case. */
|
||||
// Since we're using pooled arrays, we can rent the buffer with the
|
||||
// default size immediately, we don't need to use lazy initialization
|
||||
// to save unnecessary memory allocations in this case.
|
||||
this.array = ArrayPool<T>.Shared.Rent(DefaultInitialBufferSize);
|
||||
this.index = 0;
|
||||
}
|
||||
|
@ -86,22 +83,20 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
|
|||
/// <inheritdoc/>
|
||||
Memory<T> IMemoryOwner<T>.Memory
|
||||
{
|
||||
/* This property is explicitly implemented so that it's hidden
|
||||
* under normal usage, as the name could be confusing when
|
||||
* displayed besides WrittenMemory and GetMemory().
|
||||
* The IMemoryOwner<T> interface is implemented primarily
|
||||
* so that the AsStream() extension can be used on this type,
|
||||
* allowing users to first create a ArrayPoolBufferWriter<byte>
|
||||
* instance to write data to, then get a stream through the
|
||||
* extension and let it take care of returning the underlying
|
||||
* buffer to the shared pool when it's no longer necessary.
|
||||
* Inlining is not needed here since this will always be a callvirt. */
|
||||
// This property is explicitly implemented so that it's hidden
|
||||
// under normal usage, as the name could be confusing when
|
||||
// displayed besides WrittenMemory and GetMemory().
|
||||
// The IMemoryOwner<T> interface is implemented primarily
|
||||
// so that the AsStream() extension can be used on this type,
|
||||
// allowing users to first create a ArrayPoolBufferWriter<byte>
|
||||
// instance to write data to, then get a stream through the
|
||||
// extension and let it take care of returning the underlying
|
||||
// buffer to the shared pool when it's no longer necessary.
|
||||
// Inlining is not needed here since this will always be a callvirt.
|
||||
get => MemoryMarshal.AsMemory(WrittenMemory);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the data written to the underlying buffer so far, as a <see cref="ReadOnlyMemory{T}"/>.
|
||||
/// </summary>
|
||||
/// <inheritdoc/>
|
||||
public ReadOnlyMemory<T> WrittenMemory
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
|
@ -118,9 +113,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the data written to the underlying buffer so far, as a <see cref="ReadOnlySpan{T}"/>.
|
||||
/// </summary>
|
||||
/// <inheritdoc/>
|
||||
public ReadOnlySpan<T> WrittenSpan
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
|
@ -137,18 +130,14 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the amount of data written to the underlying buffer so far.
|
||||
/// </summary>
|
||||
/// <inheritdoc/>
|
||||
public int WrittenCount
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => this.index;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the total amount of space within the underlying buffer.
|
||||
/// </summary>
|
||||
/// <inheritdoc/>
|
||||
public int Capacity
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
|
@ -165,9 +154,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the amount of space available that can still be written into without forcing the underlying buffer to grow.
|
||||
/// </summary>
|
||||
/// <inheritdoc/>
|
||||
public int FreeCapacity
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
|
@ -184,12 +171,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the data written to the underlying buffer.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// You must clear the <see cref="ArrayPoolBufferWriter{T}"/> before trying to re-use it.
|
||||
/// </remarks>
|
||||
/// <inheritdoc/>
|
||||
public void Clear()
|
||||
{
|
||||
T[]? array = this.array;
|
||||
|
@ -308,7 +290,6 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
|
|||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the initial capacity is invalid.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1204", Justification = "Exception throwers at the end of class")]
|
||||
private static void ThrowArgumentOutOfRangeExceptionForInitialCapacity()
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("initialCapacity", "The initial capacity must be a positive value");
|
||||
|
|
|
@ -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;
|
||||
using System.Buffers;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Buffers
|
||||
{
|
||||
/// <summary>
|
||||
/// An interface that expands <see cref="IBufferWriter{T}"/> with the ability to also inspect
|
||||
/// the written data, and to reset the underlying buffer to write again from the start.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items in the current buffer.</typeparam>
|
||||
public interface IBuffer<T> : IBufferWriter<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the data written to the underlying buffer so far, as a <see cref="ReadOnlyMemory{T}"/>.
|
||||
/// </summary>
|
||||
ReadOnlyMemory<T> WrittenMemory { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the data written to the underlying buffer so far, as a <see cref="ReadOnlySpan{T}"/>.
|
||||
/// </summary>
|
||||
ReadOnlySpan<T> WrittenSpan { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the amount of data written to the underlying buffer so far.
|
||||
/// </summary>
|
||||
int WrittenCount { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the total amount of space within the underlying buffer.
|
||||
/// </summary>
|
||||
int Capacity { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the amount of space available that can still be written into without forcing the underlying buffer to grow.
|
||||
/// </summary>
|
||||
int FreeCapacity { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Clears the data written to the underlying buffer.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// You must clear the <see cref="IBuffer{T}"/> instance before trying to re-use it.
|
||||
/// </remarks>
|
||||
void Clear();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,195 @@
|
|||
// 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.Diagnostics;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Buffers
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an utput sink into which <typeparamref name="T"/> data can be written, backed by a <see cref="Memory{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items to write to the current instance.</typeparam>
|
||||
/// <remarks>
|
||||
/// This is a custom <see cref="IBufferWriter{T}"/> implementation that wraps a <see cref="Memory{T}"/> instance.
|
||||
/// It can be used to bridge APIs consuming an <see cref="IBufferWriter{T}"/> with existing <see cref="Memory{T}"/>
|
||||
/// instances (or objects that can be converted to a <see cref="Memory{T}"/>), to ensure the data is written directly
|
||||
/// to the intended buffer, with no possibility of doing additional allocations or expanding the available capacity.
|
||||
/// </remarks>
|
||||
[DebuggerTypeProxy(typeof(MemoryBufferWriter<>))]
|
||||
[DebuggerDisplay("{ToString(),raw}")]
|
||||
public sealed class MemoryBufferWriter<T> : IBuffer<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// The underlying <see cref="Memory{T}"/> instance.
|
||||
/// </summary>
|
||||
private readonly Memory<T> memory;
|
||||
|
||||
#pragma warning disable IDE0032 // Use field over auto-property (like in ArrayPoolBufferWriter<T>)
|
||||
/// <summary>
|
||||
/// The starting offset within <see cref="memory"/>.
|
||||
/// </summary>
|
||||
private int index;
|
||||
#pragma warning restore IDE0032
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MemoryBufferWriter{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="memory">The target <see cref="Memory{T}"/> instance to write to.</param>
|
||||
public MemoryBufferWriter(Memory<T> memory)
|
||||
{
|
||||
this.memory = memory;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ReadOnlyMemory<T> WrittenMemory
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => this.memory.Slice(0, this.index);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ReadOnlySpan<T> WrittenSpan
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => this.memory.Slice(0, this.index).Span;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int WrittenCount
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => this.index;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int Capacity
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => this.memory.Length;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int FreeCapacity
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => this.memory.Length - this.index;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Clear()
|
||||
{
|
||||
this.memory.Slice(0, this.index).Span.Clear();
|
||||
this.index = 0;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Advance(int count)
|
||||
{
|
||||
if (count < 0)
|
||||
{
|
||||
ThrowArgumentOutOfRangeExceptionForNegativeCount();
|
||||
}
|
||||
|
||||
if (this.index > this.memory.Length - count)
|
||||
{
|
||||
ThrowArgumentExceptionForAdvancedTooFar();
|
||||
}
|
||||
|
||||
this.index += count;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Memory<T> GetMemory(int sizeHint = 0)
|
||||
{
|
||||
this.ValidateSizeHint(sizeHint);
|
||||
|
||||
return this.memory.Slice(this.index);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Span<T> GetSpan(int sizeHint = 0)
|
||||
{
|
||||
this.ValidateSizeHint(sizeHint);
|
||||
|
||||
return this.memory.Slice(this.index).Span;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates the requested size for either <see cref="GetMemory"/> or <see cref="GetSpan"/>.
|
||||
/// </summary>
|
||||
/// <param name="sizeHint">The minimum number of items to ensure space for in <see cref="memory"/>.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void ValidateSizeHint(int sizeHint)
|
||||
{
|
||||
if (sizeHint < 0)
|
||||
{
|
||||
ThrowArgumentOutOfRangeExceptionForNegativeSizeHint();
|
||||
}
|
||||
|
||||
if (sizeHint == 0)
|
||||
{
|
||||
sizeHint = 1;
|
||||
}
|
||||
|
||||
if (sizeHint > FreeCapacity)
|
||||
{
|
||||
ThrowArgumentExceptionForCapacityExceeded();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[Pure]
|
||||
public override string ToString()
|
||||
{
|
||||
// See comments in MemoryOwner<T> about this
|
||||
if (typeof(T) == typeof(char))
|
||||
{
|
||||
return this.memory.Slice(0, this.index).ToString();
|
||||
}
|
||||
|
||||
// Same representation used in Span<T>
|
||||
return $"Microsoft.Toolkit.HighPerformance.Buffers.MemoryBufferWriter<{typeof(T)}>[{this.index}]";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the requested count is negative.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void ThrowArgumentOutOfRangeExceptionForNegativeCount()
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("count", "The count can't be a negative value");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the size hint is negative.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void ThrowArgumentOutOfRangeExceptionForNegativeSizeHint()
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("sizeHint", "The size hint can't be a negative value");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the requested count is negative.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void ThrowArgumentExceptionForAdvancedTooFar()
|
||||
{
|
||||
throw new ArgumentException("The buffer writer has advanced too far");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when the requested size exceeds the capacity.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void ThrowArgumentExceptionForCapacityExceeded()
|
||||
{
|
||||
throw new ArgumentException("The buffer writer doesn't have enough capacity left");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,14 +5,11 @@
|
|||
using System;
|
||||
using System.Buffers;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.Toolkit.HighPerformance.Buffers.Views;
|
||||
using Microsoft.Toolkit.HighPerformance.Extensions;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Buffers
|
||||
{
|
||||
/// <summary>
|
||||
|
@ -237,10 +234,10 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
|
|||
[Pure]
|
||||
public override string ToString()
|
||||
{
|
||||
/* Normally we would throw if the array has been disposed,
|
||||
* but in this case we'll just return the non formatted
|
||||
* representation as a fallback, since the ToString method
|
||||
* is generally expected not to throw exceptions. */
|
||||
// Normally we would throw if the array has been disposed,
|
||||
// but in this case we'll just return the non formatted
|
||||
// representation as a fallback, since the ToString method
|
||||
// is generally expected not to throw exceptions.
|
||||
if (typeof(T) == typeof(char) &&
|
||||
this.array is char[] chars)
|
||||
{
|
||||
|
@ -255,7 +252,6 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
|
|||
/// Throws an <see cref="ObjectDisposedException"/> when <see cref="array"/> is <see langword="null"/>.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1204", Justification = "Exception throwers at the end of class")]
|
||||
private static void ThrowObjectDisposedException()
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(MemoryOwner<T>), "The current buffer has already been disposed");
|
||||
|
|
|
@ -5,14 +5,11 @@
|
|||
using System;
|
||||
using System.Buffers;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.Toolkit.HighPerformance.Buffers.Views;
|
||||
using Microsoft.Toolkit.HighPerformance.Extensions;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Buffers
|
||||
{
|
||||
/// <summary>
|
||||
|
@ -36,7 +33,6 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
|
|||
/// <typeparam name="T">The type of items to store in the current instance.</typeparam>
|
||||
[DebuggerTypeProxy(typeof(SpanOwnerDebugView<>))]
|
||||
[DebuggerDisplay("{ToString(),raw}")]
|
||||
[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1206", Justification = "The type is a ref struct")]
|
||||
public readonly ref struct SpanOwner<T>
|
||||
{
|
||||
#pragma warning disable IDE0032
|
||||
|
|
|
@ -4,14 +4,12 @@
|
|||
|
||||
using System.Diagnostics;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Buffers.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// A debug proxy used to display items for the <see cref="ArrayPoolBufferWriter{T}"/> type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items stored in the input <see cref="ArrayPoolBufferWriter{T}"/> instances..</typeparam>
|
||||
/// <typeparam name="T">The type of items stored in the input <see cref="ArrayPoolBufferWriter{T}"/> instances.</typeparam>
|
||||
internal sealed class ArrayPoolBufferWriterDebugView<T>
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
// 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.Diagnostics;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Buffers.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// A debug proxy used to display items for the <see cref="MemoryBufferWriter{T}"/> type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items stored in the input <see cref="ArrayPoolBufferWriter{T}"/> instances.</typeparam>
|
||||
internal sealed class MemoryBufferWriterDebugView<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MemoryBufferWriterDebugView{T}"/> class with the specified parameters.
|
||||
/// </summary>
|
||||
/// <param name="memoryBufferWriter">The input <see cref="MemoryBufferWriter{T}"/> instance with the items to display.</param>
|
||||
public MemoryBufferWriterDebugView(MemoryBufferWriter<T>? memoryBufferWriter)
|
||||
{
|
||||
this.Items = memoryBufferWriter?.WrittenSpan.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the items to display for the current instance
|
||||
/// </summary>
|
||||
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
|
||||
public T[]? Items { get; }
|
||||
}
|
||||
}
|
|
@ -4,14 +4,12 @@
|
|||
|
||||
using System.Diagnostics;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Buffers.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// A debug proxy used to display items for the <see cref="MemoryOwner{T}"/> type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items stored in the input <see cref="MemoryOwner{T}"/> instances..</typeparam>
|
||||
/// <typeparam name="T">The type of items stored in the input <see cref="MemoryOwner{T}"/> instances.</typeparam>
|
||||
internal sealed class MemoryOwnerDebugView<T>
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
@ -4,14 +4,12 @@
|
|||
|
||||
using System.Diagnostics;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Buffers.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// A debug proxy used to display items for the <see cref="SpanOwner{T}"/> type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items stored in the input <see cref="SpanOwner{T}"/> instances..</typeparam>
|
||||
/// <typeparam name="T">The type of items stored in the input <see cref="SpanOwner{T}"/> instances.</typeparam>
|
||||
internal sealed class SpanOwnerDebugView<T>
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.Toolkit.HighPerformance.Extensions;
|
||||
|
@ -16,7 +15,6 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
|
|||
/// A <see langword="ref"/> <see langword="struct"/> that iterates a column in a given 2D <typeparamref name="T"/> array instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items to enumerate.</typeparam>
|
||||
[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1206", Justification = "The type is a ref struct")]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public readonly ref struct Array2DColumnEnumerable<T>
|
||||
{
|
||||
|
@ -69,9 +67,13 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
|
|||
|
||||
T[] array = new T[height];
|
||||
|
||||
for (int i = 0; i < height; i++)
|
||||
ref T r0 = ref array.DangerousGetReference();
|
||||
int i = 0;
|
||||
|
||||
// Leverage the enumerator to traverse the column
|
||||
foreach (T item in this)
|
||||
{
|
||||
array.DangerousGetReferenceAt(i) = this.array.DangerousGetReferenceAt(i, this.column);
|
||||
Unsafe.Add(ref r0, i++) = item;
|
||||
}
|
||||
|
||||
return array;
|
||||
|
@ -83,6 +85,31 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
|
|||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public ref struct Enumerator
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
/// <summary>
|
||||
/// The <see cref="Span{T}"/> instance mapping the target 2D array.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// In runtimes where we have support for the <see cref="Span{T}"/> type, we can
|
||||
/// create one from the input 2D array and use that to traverse the target column.
|
||||
/// This reduces the number of operations to perform for the offsetting to the right
|
||||
/// column element (we simply need to add <see cref="width"/> to the offset at each
|
||||
/// iteration to move down by one row), and allows us to use the fast <see cref="Span{T}"/>
|
||||
/// accessor instead of the slower indexer for 2D arrays, as we can then access each
|
||||
/// individual item linearly, since we know the absolute offset from the base location.
|
||||
/// </remarks>
|
||||
private readonly Span<T> span;
|
||||
|
||||
/// <summary>
|
||||
/// The width of the target 2D array.
|
||||
/// </summary>
|
||||
private readonly int width;
|
||||
|
||||
/// <summary>
|
||||
/// The current absolute offset within <see cref="span"/>.
|
||||
/// </summary>
|
||||
private int offset;
|
||||
#else
|
||||
/// <summary>
|
||||
/// The source 2D array instance.
|
||||
/// </summary>
|
||||
|
@ -102,6 +129,7 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
|
|||
/// The current row.
|
||||
/// </summary>
|
||||
private int row;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Enumerator"/> struct.
|
||||
|
@ -116,10 +144,16 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
|
|||
ThrowArgumentOutOfRangeExceptionForInvalidColumn();
|
||||
}
|
||||
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
this.span = array.AsSpan();
|
||||
this.width = array.GetLength(1);
|
||||
this.offset = column - this.width;
|
||||
#else
|
||||
this.array = array;
|
||||
this.column = column;
|
||||
this.height = array.GetLength(0);
|
||||
this.row = -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -129,6 +163,16 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool MoveNext()
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
int offset = this.offset + this.width;
|
||||
|
||||
if ((uint)offset < (uint)this.span.Length)
|
||||
{
|
||||
this.offset = offset;
|
||||
|
||||
return true;
|
||||
}
|
||||
#else
|
||||
int row = this.row + 1;
|
||||
|
||||
if (row < this.height)
|
||||
|
@ -137,7 +181,7 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -147,7 +191,14 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
|
|||
public ref T Current
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => ref this.array.DangerousGetReferenceAt(this.row, this.column);
|
||||
get
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
return ref this.span.DangerousGetReferenceAt(this.offset);
|
||||
#else
|
||||
return ref this.array[this.row, this.column];
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -160,4 +211,4 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
|
|||
throw new ArgumentOutOfRangeException(nameof(column), "The target column parameter was not valid");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,12 +2,11 @@
|
|||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
#if NETSTANDARD2_0
|
||||
#if !SPAN_RUNTIME_SUPPORT
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.Toolkit.HighPerformance.Extensions;
|
||||
|
@ -18,7 +17,6 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
|
|||
/// A <see langword="ref"/> <see langword="struct"/> that iterates a row in a given 2D <typeparamref name="T"/> array instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items to enumerate.</typeparam>
|
||||
[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1206", Justification = "The type is a ref struct")]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public readonly ref struct Array2DRowEnumerable<T>
|
||||
{
|
||||
|
@ -149,7 +147,13 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
|
|||
public ref T Current
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => ref this.array.DangerousGetReferenceAt(this.row, this.column);
|
||||
get
|
||||
{
|
||||
// This type is never used on .NET Core runtimes, where
|
||||
// the fast indexer is available. Therefore, we can just
|
||||
// use the built-in indexer for 2D arrays to access the value.
|
||||
return ref this.array[this.row, this.column];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
|
@ -15,7 +14,6 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
|
|||
/// A <see langword="ref"/> <see langword="struct"/> that enumerates the items in a given <see cref="ReadOnlySpan{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items to enumerate.</typeparam>
|
||||
[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1206", Justification = "The type is a ref struct")]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public readonly ref struct ReadOnlySpanEnumerable<T>
|
||||
{
|
||||
|
@ -90,16 +88,97 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
|
|||
/// <summary>
|
||||
/// Gets the duck-typed <see cref="IEnumerator{T}.Current"/> property.
|
||||
/// </summary>
|
||||
[SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1008", Justification = "ValueTuple<T1,T2> return type")]
|
||||
public (int Index, T Value) Current
|
||||
public Item Current
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
int currentIndex = this.index;
|
||||
T value = Unsafe.Add(ref MemoryMarshal.GetReference(this.span), currentIndex);
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
ref T r0 = ref MemoryMarshal.GetReference(this.span);
|
||||
ref T ri = ref Unsafe.Add(ref r0, this.index);
|
||||
|
||||
return (currentIndex, value);
|
||||
// See comment in SpanEnumerable<T> about this
|
||||
return new Item(ref ri, this.index);
|
||||
#else
|
||||
return new Item(this.span, this.index);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An item from a source <see cref="Span{T}"/> instance.
|
||||
/// </summary>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public readonly ref struct Item
|
||||
{
|
||||
/// <summary>
|
||||
/// The source <see cref="ReadOnlySpan{T}"/> instance.
|
||||
/// </summary>
|
||||
private readonly ReadOnlySpan<T> span;
|
||||
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Item"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="value">A reference to the target value.</param>
|
||||
/// <param name="index">The index of the target value.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Item(ref T value, int index)
|
||||
{
|
||||
this.span = MemoryMarshal.CreateReadOnlySpan(ref value, index);
|
||||
}
|
||||
#else
|
||||
/// <summary>
|
||||
/// The current index within <see cref="span"/>.
|
||||
/// </summary>
|
||||
private readonly int index;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Item"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="span">The source <see cref="ReadOnlySpan{T}"/> instance.</param>
|
||||
/// <param name="index">The current index within <paramref name="span"/>.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Item(ReadOnlySpan<T> span, int index)
|
||||
{
|
||||
this.span = span;
|
||||
this.index = index;
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Gets the reference to the current value.
|
||||
/// </summary>
|
||||
public ref readonly T Value
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
return ref MemoryMarshal.GetReference(this.span);
|
||||
#else
|
||||
ref T r0 = ref MemoryMarshal.GetReference(this.span);
|
||||
ref T ri = ref Unsafe.Add(ref r0, this.index);
|
||||
|
||||
return ref ri;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current index.
|
||||
/// </summary>
|
||||
public int Index
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
return this.span.Length;
|
||||
#else
|
||||
return this.index;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Enumerables
|
||||
|
@ -14,7 +13,6 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
|
|||
/// A <see langword="ref"/> <see langword="struct"/> that tokenizes a given <see cref="ReadOnlySpan{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items to enumerate.</typeparam>
|
||||
[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1206", Justification = "The type is a ref struct")]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public readonly ref struct ReadOnlySpanTokenizer<T>
|
||||
where T : IEquatable<T>
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
|
@ -15,7 +14,6 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
|
|||
/// A <see langword="ref"/> <see langword="struct"/> that enumerates the items in a given <see cref="Span{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items to enumerate.</typeparam>
|
||||
[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1206", Justification = "The type is a ref struct")]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public readonly ref struct SpanEnumerable<T>
|
||||
{
|
||||
|
@ -95,16 +93,16 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
#if NETSTANDARD2_1
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
ref T r0 = ref MemoryMarshal.GetReference(this.span);
|
||||
ref T ri = ref Unsafe.Add(ref r0, this.index);
|
||||
|
||||
/* On .NET Standard 2.1 we can save 4 bytes by piggybacking
|
||||
* the current index in the length of the wrapped span.
|
||||
* We're going to use the first item as the target reference,
|
||||
* and the length as a host for the current original offset.
|
||||
* This is not possible on .NET Standard 2.1 as we lack
|
||||
* the API to create spans from arbitrary references. */
|
||||
// On .NET Standard 2.1 we can save 4 bytes by piggybacking
|
||||
// the current index in the length of the wrapped span.
|
||||
// We're going to use the first item as the target reference,
|
||||
// and the length as a host for the current original offset.
|
||||
// This is not possible on .NET Standard 2.1 as we lack
|
||||
// the API to create spans from arbitrary references.
|
||||
return new Item(ref ri, this.index);
|
||||
#else
|
||||
return new Item(this.span, this.index);
|
||||
|
@ -124,7 +122,7 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
|
|||
/// </summary>
|
||||
private readonly Span<T> span;
|
||||
|
||||
#if NETSTANDARD2_1
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Item"/> struct.
|
||||
/// </summary>
|
||||
|
@ -162,7 +160,7 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
#if NETSTANDARD2_1
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
return ref MemoryMarshal.GetReference(this.span);
|
||||
#else
|
||||
ref T r0 = ref MemoryMarshal.GetReference(this.span);
|
||||
|
@ -181,7 +179,7 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
#if NETSTANDARD2_1
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
return this.span.Length;
|
||||
#else
|
||||
return this.index;
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Enumerables
|
||||
|
@ -14,7 +13,6 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
|
|||
/// A <see langword="ref"/> <see langword="struct"/> that tokenizes a given <see cref="Span{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items to enumerate.</typeparam>
|
||||
[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1206", Justification = "The type is a ref struct")]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public readonly ref struct SpanTokenizer<T>
|
||||
where T : IEquatable<T>
|
||||
|
|
|
@ -6,10 +6,11 @@ using System;
|
|||
using System.Diagnostics.Contracts;
|
||||
using System.Drawing;
|
||||
using System.Runtime.CompilerServices;
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
using System.Runtime.InteropServices;
|
||||
#endif
|
||||
using Microsoft.Toolkit.HighPerformance.Enumerables;
|
||||
|
||||
#nullable enable
|
||||
using Microsoft.Toolkit.HighPerformance.Helpers.Internals;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Extensions
|
||||
{
|
||||
|
@ -29,10 +30,24 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ref T DangerousGetReference<T>(this T[,] array)
|
||||
{
|
||||
#if NETCORE_RUNTIME
|
||||
var arrayData = Unsafe.As<RawArray2DData>(array);
|
||||
ref T r0 = ref Unsafe.As<byte, T>(ref arrayData.Data);
|
||||
|
||||
return ref r0;
|
||||
#else
|
||||
#pragma warning disable SA1131 // Inverted comparison to remove JIT bounds check
|
||||
if (0u < (uint)array.Length)
|
||||
{
|
||||
return ref array[0, 0];
|
||||
}
|
||||
|
||||
unsafe
|
||||
{
|
||||
return ref Unsafe.AsRef<T>(null);
|
||||
}
|
||||
#pragma warning restore SA1131
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -53,14 +68,28 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ref T DangerousGetReferenceAt<T>(this T[,] array, int i, int j)
|
||||
{
|
||||
#if NETCORE_RUNTIME
|
||||
var arrayData = Unsafe.As<RawArray2DData>(array);
|
||||
int offset = (i * arrayData.Width) + j;
|
||||
ref T r0 = ref Unsafe.As<byte, T>(ref arrayData.Data);
|
||||
ref T ri = ref Unsafe.Add(ref r0, offset);
|
||||
|
||||
return ref ri;
|
||||
#else
|
||||
if ((uint)i < (uint)array.GetLength(0) &&
|
||||
(uint)j < (uint)array.GetLength(1))
|
||||
{
|
||||
return ref array[i, j];
|
||||
}
|
||||
|
||||
unsafe
|
||||
{
|
||||
return ref Unsafe.AsRef<T>(null);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if NETCORE_RUNTIME
|
||||
// Description adapted from CoreCLR: see https://source.dot.net/#System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs,285.
|
||||
// CLR 2D arrays are laid out in memory as follows:
|
||||
// [ sync block || pMethodTable || Length (padded to IntPtr) || HxW || HxW bounds || array data .. ]
|
||||
|
@ -83,6 +112,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
#pragma warning restore CS0649
|
||||
#pragma warning restore SA1401
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Fills an area in a given 2D <typeparamref name="T"/> array instance with a specified value.
|
||||
|
@ -105,20 +135,24 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
|
||||
for (int i = bounds.Top; i < bounds.Bottom; i++)
|
||||
{
|
||||
#if NETSTANDARD2_1
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
#if NETCORE_RUNTIME
|
||||
ref T r0 = ref array.DangerousGetReferenceAt(i, bounds.Left);
|
||||
#else
|
||||
ref T r0 = ref array[i, bounds.Left];
|
||||
#endif
|
||||
|
||||
// Span<T>.Fill will use vectorized instructions when possible
|
||||
MemoryMarshal.CreateSpan(ref r0, bounds.Width).Fill(value);
|
||||
#else
|
||||
ref T r0 = ref array.DangerousGetReferenceAt(i, bounds.Left);
|
||||
ref T r0 = ref array[i, bounds.Left];
|
||||
|
||||
for (int j = 0; j < bounds.Width; j++)
|
||||
{
|
||||
/* Storing the initial reference and only incrementing
|
||||
* that one in each iteration saves one additional indirect
|
||||
* dereference for every loop iteration compared to using
|
||||
* the DangerousGetReferenceAt<T> extension on the array. */
|
||||
// Storing the initial reference and only incrementing
|
||||
// that one in each iteration saves one additional indirect
|
||||
// dereference for every loop iteration compared to using
|
||||
// the DangerousGetReferenceAt<T> extension on the array.
|
||||
Unsafe.Add(ref r0, j) = value;
|
||||
}
|
||||
#endif
|
||||
|
@ -135,21 +169,21 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static
|
||||
#if NETSTANDARD2_1
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
Span<T>
|
||||
#else
|
||||
/* .NET Standard 2.0 lacks MemoryMarshal.CreateSpan<T>(ref T, int),
|
||||
* which is necessary to create arbitrary Span<T>-s over a 2D array.
|
||||
* To work around this, we use a custom ref struct enumerator,
|
||||
* which makes the lack of that API completely transparent to the user.
|
||||
* If a user then moves from .NET Standard 2.0 to 2.1, all the previous
|
||||
* features will be perfectly supported, and in addition to that it will
|
||||
* also gain the ability to use the Span<T> value elsewhere.
|
||||
* The only case where this would be a breaking change for a user upgrading
|
||||
* the target framework is when the returned enumerator type is used directly,
|
||||
* but since that's specifically discouraged from the docs, we don't
|
||||
* need to worry about that scenario in particular, as users doing that
|
||||
* would be willingly go against the recommended usage of this API. */
|
||||
// .NET Standard 2.0 lacks MemoryMarshal.CreateSpan<T>(ref T, int),
|
||||
// which is necessary to create arbitrary Span<T>-s over a 2D array.
|
||||
// To work around this, we use a custom ref struct enumerator,
|
||||
// which makes the lack of that API completely transparent to the user.
|
||||
// If a user then moves from .NET Standard 2.0 to 2.1, all the previous
|
||||
// features will be perfectly supported, and in addition to that it will
|
||||
// also gain the ability to use the Span<T> value elsewhere.
|
||||
// The only case where this would be a breaking change for a user upgrading
|
||||
// the target framework is when the returned enumerator type is used directly,
|
||||
// but since that's specifically discouraged from the docs, we don't
|
||||
// need to worry about that scenario in particular, as users doing that
|
||||
// would be willingly go against the recommended usage of this API.
|
||||
Array2DRowEnumerable<T>
|
||||
#endif
|
||||
GetRow<T>(this T[,] array, int row)
|
||||
|
@ -159,7 +193,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
throw new ArgumentOutOfRangeException(nameof(row));
|
||||
}
|
||||
|
||||
#if NETSTANDARD2_1
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
ref T r0 = ref array.DangerousGetReferenceAt(row, 0);
|
||||
|
||||
return MemoryMarshal.CreateSpan(ref r0, array.GetLength(1));
|
||||
|
@ -198,7 +232,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
return new Array2DColumnEnumerable<T>(array, column);
|
||||
}
|
||||
|
||||
#if NETSTANDARD2_1
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
/// <summary>
|
||||
/// Cretes a new <see cref="Span{T}"/> over an input 2D <typeparamref name="T"/> array.
|
||||
/// </summary>
|
||||
|
@ -209,22 +243,27 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Span<T> AsSpan<T>(this T[,] array)
|
||||
{
|
||||
#if NETCORE_RUNTIME
|
||||
var arrayData = Unsafe.As<RawArray2DData>(array);
|
||||
|
||||
/* On x64, the length is padded to x64, but it is represented in memory
|
||||
* as two sequential uint fields (one of which is padding).
|
||||
* So we can just reinterpret a reference to the IntPtr as one of type
|
||||
* uint, to access the first 4 bytes of that field, regardless of whether
|
||||
* we're running in a 32 or 64 bit process. This will work when on little
|
||||
* endian systems as well, as the memory layout for fields is the same,
|
||||
* the only difference is the order of bytes within each field of a given type.
|
||||
* We use checked here to follow suit with the CoreCLR source, where an
|
||||
* invalid value here should fail to perform the cast and throw an exception. */
|
||||
// On x64, the length is padded to x64, but it is represented in memory
|
||||
// as two sequential uint fields (one of which is padding).
|
||||
// So we can just reinterpret a reference to the IntPtr as one of type
|
||||
// uint, to access the first 4 bytes of that field, regardless of whether
|
||||
// we're running in a 32 or 64 bit process. This will work when on little
|
||||
// endian systems as well, as the memory layout for fields is the same,
|
||||
// the only difference is the order of bytes within each field of a given type.
|
||||
// We use checked here to follow suit with the CoreCLR source, where an
|
||||
// invalid value here should fail to perform the cast and throw an exception.
|
||||
int length = checked((int)Unsafe.As<IntPtr, uint>(ref arrayData.Length));
|
||||
ref T r0 = ref Unsafe.As<byte, T>(ref arrayData.Data);
|
||||
|
||||
#else
|
||||
int length = array.Length;
|
||||
ref T r0 = ref array[0, 0];
|
||||
#endif
|
||||
return MemoryMarshal.CreateSpan(ref r0, length);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Counts the number of occurrences of a given value into a target 2D <typeparamref name="T"/> array instance.
|
||||
|
@ -238,11 +277,15 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
public static int Count<T>(this T[,] array, T value)
|
||||
where T : IEquatable<T>
|
||||
{
|
||||
return ReadOnlySpanExtensions.Count(array.AsSpan(), value);
|
||||
ref T r0 = ref array.DangerousGetReference();
|
||||
IntPtr length = (IntPtr)array.Length;
|
||||
|
||||
return SpanHelper.Count(ref r0, length, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a content hash from the input 2D <typeparamref name="T"/> array instance using the Djb2 algorithm.
|
||||
/// For more info, see the documentation for <see cref="ReadOnlySpanExtensions.GetDjb2HashCode{T}"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items in the input 2D <typeparamref name="T"/> array instance.</typeparam>
|
||||
/// <param name="array">The input 2D <typeparamref name="T"/> array instance.</param>
|
||||
|
@ -253,8 +296,10 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
public static int GetDjb2HashCode<T>(this T[,] array)
|
||||
where T : notnull
|
||||
{
|
||||
return ReadOnlySpanExtensions.GetDjb2HashCode<T>(array.AsSpan());
|
||||
ref T r0 = ref array.DangerousGetReference();
|
||||
IntPtr length = (IntPtr)array.Length;
|
||||
|
||||
return SpanHelper.GetDjb2HashCode(ref r0, length);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,10 +5,11 @@
|
|||
using System;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
#if NETCORE_RUNTIME
|
||||
using System.Runtime.InteropServices;
|
||||
#endif
|
||||
using Microsoft.Toolkit.HighPerformance.Enumerables;
|
||||
|
||||
#nullable enable
|
||||
using Microsoft.Toolkit.HighPerformance.Helpers.Internals;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Extensions
|
||||
{
|
||||
|
@ -28,10 +29,27 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ref T DangerousGetReference<T>(this T[] array)
|
||||
{
|
||||
#if NETCORE_RUNTIME
|
||||
var arrayData = Unsafe.As<RawArrayData>(array);
|
||||
ref T r0 = ref Unsafe.As<byte, T>(ref arrayData.Data);
|
||||
|
||||
return ref r0;
|
||||
#else
|
||||
#pragma warning disable SA1131 // Inverted comparison to remove JIT bounds check
|
||||
// Checking the length of the array like so allows the JIT
|
||||
// to skip its own bounds check, which results in the element
|
||||
// access below to be executed without branches.
|
||||
if (0u < (uint)array.Length)
|
||||
{
|
||||
return ref array[0];
|
||||
}
|
||||
|
||||
unsafe
|
||||
{
|
||||
return ref Unsafe.AsRef<T>(null);
|
||||
}
|
||||
#pragma warning restore SA1131
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -46,13 +64,26 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ref T DangerousGetReferenceAt<T>(this T[] array, int i)
|
||||
{
|
||||
#if NETCORE_RUNTIME
|
||||
var arrayData = Unsafe.As<RawArrayData>(array);
|
||||
ref T r0 = ref Unsafe.As<byte, T>(ref arrayData.Data);
|
||||
ref T ri = ref Unsafe.Add(ref r0, i);
|
||||
|
||||
return ref ri;
|
||||
#else
|
||||
if ((uint)i < (uint)array.Length)
|
||||
{
|
||||
return ref array[i];
|
||||
}
|
||||
|
||||
unsafe
|
||||
{
|
||||
return ref Unsafe.AsRef<T>(null);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if NETCORE_RUNTIME
|
||||
// Description taken from CoreCLR: see https://source.dot.net/#System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs,285.
|
||||
// CLR arrays are laid out in memory as follows (multidimensional array bounds are optional):
|
||||
// [ sync block || pMethodTable || num components || MD array bounds || array data .. ]
|
||||
|
@ -72,6 +103,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
#pragma warning restore CS0649
|
||||
#pragma warning restore SA1401
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Counts the number of occurrences of a given value into a target <typeparamref name="T"/> array instance.
|
||||
|
@ -85,7 +117,10 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
public static int Count<T>(this T[] array, T value)
|
||||
where T : IEquatable<T>
|
||||
{
|
||||
return ReadOnlySpanExtensions.Count(array, value);
|
||||
ref T r0 = ref array.DangerousGetReference();
|
||||
IntPtr length = (IntPtr)array.Length;
|
||||
|
||||
return SpanHelper.Count(ref r0, length, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -142,6 +177,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
|
||||
/// <summary>
|
||||
/// Gets a content hash from the input <typeparamref name="T"/> array instance using the Djb2 algorithm.
|
||||
/// For more info, see the documentation for <see cref="ReadOnlySpanExtensions.GetDjb2HashCode{T}"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items in the input <typeparamref name="T"/> array instance.</typeparam>
|
||||
/// <param name="array">The input <typeparamref name="T"/> array instance.</param>
|
||||
|
@ -152,7 +188,10 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
public static int GetDjb2HashCode<T>(this T[] array)
|
||||
where T : notnull
|
||||
{
|
||||
return ReadOnlySpanExtensions.GetDjb2HashCode<T>(array);
|
||||
ref T r0 = ref array.DangerousGetReference();
|
||||
IntPtr length = (IntPtr)array.Length;
|
||||
|
||||
return SpanHelper.GetDjb2HashCode(ref r0, length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,8 +5,6 @@
|
|||
using System;
|
||||
using System.Buffers;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
|
@ -40,11 +38,11 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
return;
|
||||
}
|
||||
|
||||
/* Rent a new array with the specified size, and copy as many items from the current array
|
||||
* as possible to the new array. This mirrors the behavior of the Array.Resize API from
|
||||
* the BCL: if the new size is greater than the length of the current array, copy all the
|
||||
* items from the original array into the new one. Otherwise, copy as many items as possible,
|
||||
* until the new array is completely filled, and ignore the remaining items in the first array. */
|
||||
// Rent a new array with the specified size, and copy as many items from the current array
|
||||
// as possible to the new array. This mirrors the behavior of the Array.Resize API from
|
||||
// the BCL: if the new size is greater than the length of the current array, copy all the
|
||||
// items from the original array into the new one. Otherwise, copy as many items as possible,
|
||||
// until the new array is completely filled, and ignore the remaining items in the first array.
|
||||
T[] newArray = pool.Rent(newSize);
|
||||
int itemsToCopy = Math.Min(array.Length, newSize);
|
||||
|
||||
|
|
|
@ -24,5 +24,50 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
{
|
||||
return Unsafe.As<bool, byte>(ref flag);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the given <see cref="bool"/> value to an <see cref="int"/> mask with
|
||||
/// all bits representing the value of the input flag (either 0xFFFFFFFF or 0x00000000).
|
||||
/// </summary>
|
||||
/// <param name="flag">The input value to convert.</param>
|
||||
/// <returns>0xFFFFFFFF if <paramref name="flag"/> is <see langword="true"/>, 0x00000000 otherwise.</returns>
|
||||
/// <remarks>
|
||||
/// This method does not contain branching instructions, and it is only guaranteed to work with
|
||||
/// <see cref="bool"/> values being either 0 or 1. Operations producing a <see cref="bool"/> result,
|
||||
/// such as numerical comparisons, always result in a valid value. If the <see cref="bool"/> value is
|
||||
/// produced by fields with a custom <see cref="System.Runtime.InteropServices.FieldOffsetAttribute"/>,
|
||||
/// or by using <see cref="Unsafe.As{T}"/> or other unsafe APIs to directly manipulate the underlying
|
||||
/// data though, it is responsibility of the caller to ensure the validity of the provided value.
|
||||
/// </remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int ToBitwiseMask32(this bool flag)
|
||||
{
|
||||
byte rangeFlag = Unsafe.As<bool, byte>(ref flag);
|
||||
int
|
||||
negativeFlag = rangeFlag - 1,
|
||||
mask = ~negativeFlag;
|
||||
|
||||
return mask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the given <see cref="bool"/> value to a <see cref="long"/> mask with
|
||||
/// all bits representing the value of the input flag (either all 1s or 0s).
|
||||
/// </summary>
|
||||
/// <param name="flag">The input value to convert.</param>
|
||||
/// <returns>All 1s if <paramref name="flag"/> is <see langword="true"/>, all 0s otherwise.</returns>
|
||||
/// <remarks>This method does not contain branching instructions. See additional note in <see cref="ToBitwiseMask32"/>.</remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static long ToBitwiseMask64(this bool flag)
|
||||
{
|
||||
byte rangeFlag = Unsafe.As<bool, byte>(ref flag);
|
||||
long
|
||||
negativeFlag = (long)rangeFlag - 1,
|
||||
mask = ~negativeFlag;
|
||||
|
||||
return mask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,12 +2,12 @@
|
|||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
#if !NETSTANDARD1_4
|
||||
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.Toolkit.HighPerformance.Helpers;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
|
@ -23,7 +23,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
/// <param name="span">The input <see cref="ReadOnlySpan{T}"/> instance.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Add<T>(ref this HashCode hashCode, ReadOnlySpan<T> span)
|
||||
#if NETSTANDARD2_1
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
where T : notnull
|
||||
#else
|
||||
// Same type constraints as HashCode<T>, see comments there
|
||||
|
@ -36,3 +36,5 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -0,0 +1,111 @@
|
|||
// 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 Microsoft.Toolkit.HighPerformance.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers for working with the <see cref="IBufferWriter{T}"/> type.
|
||||
/// </summary>
|
||||
public static class IBufferWriterExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Writes a value of a specified type into a target <see cref="IBufferWriter{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of value to write.</typeparam>
|
||||
/// <param name="writer">The target <see cref="IBufferWriter{T}"/> instance to write to.</param>
|
||||
/// <param name="value">The input value to write to <paramref name="writer"/>.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="writer"/> reaches the end.</exception>
|
||||
public static void Write<T>(this IBufferWriter<byte> writer, T value)
|
||||
where T : unmanaged
|
||||
{
|
||||
int length = Unsafe.SizeOf<T>();
|
||||
Span<byte> span = writer.GetSpan(1);
|
||||
|
||||
if (span.Length < length)
|
||||
{
|
||||
ThrowArgumentExceptionForEndOfBuffer();
|
||||
}
|
||||
|
||||
ref byte r0 = ref MemoryMarshal.GetReference(span);
|
||||
|
||||
Unsafe.WriteUnaligned(ref r0, value);
|
||||
|
||||
writer.Advance(length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a value of a specified type into a target <see cref="IBufferWriter{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of value to write.</typeparam>
|
||||
/// <param name="writer">The target <see cref="IBufferWriter{T}"/> instance to write to.</param>
|
||||
/// <param name="value">The input value to write to <paramref name="writer"/>.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="writer"/> reaches the end.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Write<T>(this IBufferWriter<T> writer, T value)
|
||||
{
|
||||
Span<T> span = writer.GetSpan(1);
|
||||
|
||||
if (span.Length < 1)
|
||||
{
|
||||
ThrowArgumentExceptionForEndOfBuffer();
|
||||
}
|
||||
|
||||
MemoryMarshal.GetReference(span) = value;
|
||||
|
||||
writer.Advance(1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a series of items of a specified type into a target <see cref="IBufferWriter{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of value to write.</typeparam>
|
||||
/// <param name="writer">The target <see cref="IBufferWriter{T}"/> instance to write to.</param>
|
||||
/// <param name="span">The input <see cref="ReadOnlySpan{T}"/> to write to <paramref name="writer"/>.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="writer"/> reaches the end.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Write<T>(this IBufferWriter<byte> writer, ReadOnlySpan<T> span)
|
||||
where T : unmanaged
|
||||
{
|
||||
ReadOnlySpan<byte> source = MemoryMarshal.AsBytes(span);
|
||||
Span<byte> destination = writer.GetSpan(source.Length);
|
||||
|
||||
source.CopyTo(destination);
|
||||
|
||||
writer.Advance(source.Length);
|
||||
}
|
||||
|
||||
#if !SPAN_RUNTIME_SUPPORT
|
||||
/// <summary>
|
||||
/// Writes a series of items of a specified type into a target <see cref="IBufferWriter{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of value to write.</typeparam>
|
||||
/// <param name="writer">The target <see cref="IBufferWriter{T}"/> instance to write to.</param>
|
||||
/// <param name="span">The input <see cref="ReadOnlySpan{T}"/> to write to <paramref name="writer"/>.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="writer"/> reaches the end.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Write<T>(this IBufferWriter<T> writer, ReadOnlySpan<T> span)
|
||||
{
|
||||
Span<T> destination = writer.GetSpan(span.Length);
|
||||
|
||||
span.CopyTo(destination);
|
||||
|
||||
writer.Advance(span.Length);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when trying to write too many bytes to the target writer.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void ThrowArgumentExceptionForEndOfBuffer()
|
||||
{
|
||||
throw new ArgumentException("The current buffer writer can't contain the requested input data.");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,8 +8,6 @@ using System.IO;
|
|||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.Toolkit.HighPerformance.Streams;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
@ -8,8 +8,6 @@ using System.IO;
|
|||
using System.Runtime.CompilerServices;
|
||||
using MemoryStream = Microsoft.Toolkit.HighPerformance.Streams.MemoryStream;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
// 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.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
#nullable enable
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Extensions
|
||||
{
|
||||
|
@ -14,6 +14,79 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
/// </summary>
|
||||
public static class ObjectExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Calculates the byte offset to a specific field within a given <see cref="object"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of field being referenced.</typeparam>
|
||||
/// <param name="obj">The input <see cref="object"/> hosting the target field.</param>
|
||||
/// <param name="data">A reference to a target field of type <typeparamref name="T"/> within <paramref name="obj"/>.</param>
|
||||
/// <returns>
|
||||
/// The <see cref="IntPtr"/> value representing the offset to the target field from the start of the object data
|
||||
/// for the parameter <paramref name="obj"/>. The offset is in relation to the first usable byte after the method table.
|
||||
/// </returns>
|
||||
/// <remarks>The input parameters are not validated, and it's responsability of the caller to ensure that
|
||||
/// the <paramref name="data"/> reference is actually pointing to a memory location within <paramref name="obj"/>.
|
||||
/// </remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static IntPtr DangerousGetObjectDataByteOffset<T>(this object obj, ref T data)
|
||||
{
|
||||
var rawObj = Unsafe.As<RawObjectData>(obj);
|
||||
ref byte r0 = ref rawObj.Data;
|
||||
ref byte r1 = ref Unsafe.As<T, byte>(ref data);
|
||||
|
||||
return Unsafe.ByteOffset(ref r0, ref r1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <typeparamref name="T"/> reference to data within a given <see cref="object"/> at a specified offset.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of reference to retrieve.</typeparam>
|
||||
/// <param name="obj">The input <see cref="object"/> hosting the target field.</param>
|
||||
/// <param name="offset">The input byte offset for the <typeparamref name="T"/> reference to retrieve.</param>
|
||||
/// <returns>A <typeparamref name="T"/> reference at a specified offset within <paramref name="obj"/>.</returns>
|
||||
/// <remarks>
|
||||
/// None of the input arguments is validated, and it is responsability of the caller to ensure they are valid.
|
||||
/// In particular, using an invalid offset might cause the retrieved reference to be misaligned with the
|
||||
/// desired data, which would break the type system. Or, if the offset causes the retrieved reference to point
|
||||
/// to a memory location outside of the input <see cref="object"/> instance, that might lead to runtime crashes.
|
||||
/// </remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ref T DangerousGetObjectDataReferenceAt<T>(this object obj, IntPtr offset)
|
||||
{
|
||||
var rawObj = Unsafe.As<RawObjectData>(obj);
|
||||
ref byte r0 = ref rawObj.Data;
|
||||
ref byte r1 = ref Unsafe.AddByteOffset(ref r0, offset);
|
||||
ref T r2 = ref Unsafe.As<byte, T>(ref r1);
|
||||
|
||||
return ref r2;
|
||||
}
|
||||
|
||||
// Description adapted from CoreCLR, see:
|
||||
// https://source.dot.net/#System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs,301.
|
||||
// CLR objects are laid out in memory as follows:
|
||||
// [ sync block || pMethodTable || raw data .. ]
|
||||
// ^ ^
|
||||
// | \-- ref Unsafe.As<RawObjectData>(owner).Data
|
||||
// \-- object
|
||||
// The reference to RawObjectData.Data points to the first data byte in the
|
||||
// target object, skipping over the sync block, method table and string length.
|
||||
// Even though the description above links to the CoreCLR source, this approach
|
||||
// can actually work on any .NET runtime, as it doesn't rely on a specific memory
|
||||
// layout. Even if some 3rd party .NET runtime had some additional fields in the
|
||||
// object header, before the field being referenced, the returned offset would still
|
||||
// be valid when used on instances of that particular type, as it's only being
|
||||
// used as a relative offset from the location pointed by the object reference.
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
private sealed class RawObjectData
|
||||
{
|
||||
[FieldOffset(0)]
|
||||
#pragma warning disable SA1401 // Fields should be private
|
||||
public byte Data;
|
||||
#pragma warning restore SA1401
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get a boxed <typeparamref name="T"/> value from an input <see cref="object"/> instance.
|
||||
/// </summary>
|
||||
|
@ -63,10 +136,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
/// <typeparam name="T">The type of value to unbox.</typeparam>
|
||||
/// <param name="obj">The input <see cref="object"/> instance, representing a boxed <typeparamref name="T"/> value.</param>
|
||||
/// <returns>The <typeparamref name="T"/> value boxed in <paramref name="obj"/>.</returns>
|
||||
/// <remarks>
|
||||
/// This method doesn't check the actual type of <paramref name="obj"/>, so it is responsability of the caller
|
||||
/// to ensure it actually represents a boxed <typeparamref name="T"/> value and not some other instance.
|
||||
/// </remarks>
|
||||
/// <exception cref="InvalidCastException">Thrown when <paramref name="obj"/> is not of type <typeparamref name="T"/>.</exception>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ref T DangerousUnbox<T>(this object obj)
|
||||
|
|
|
@ -8,8 +8,6 @@ using System.IO;
|
|||
using System.Runtime.CompilerServices;
|
||||
using MemoryStream = Microsoft.Toolkit.HighPerformance.Streams.MemoryStream;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
@ -1,252 +0,0 @@
|
|||
// 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.Diagnostics.Contracts;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers for working with the <see cref="ReadOnlySpan{T}"/> type.
|
||||
/// </summary>
|
||||
public static partial class ReadOnlySpanExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Counts the number of occurrences of a given value into a target <see cref="ReadOnlySpan{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items in the input <see cref="ReadOnlySpan{T}"/> instance.</typeparam>
|
||||
/// <param name="span">The input <see cref="ReadOnlySpan{T}"/> instance to read.</param>
|
||||
/// <param name="value">The <typeparamref name="T"/> value to look for.</param>
|
||||
/// <returns>The number of occurrences of <paramref name="value"/> in <paramref name="span"/>.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static int Count<T>(this ReadOnlySpan<T> span, T value)
|
||||
where T : IEquatable<T>
|
||||
{
|
||||
// Special vectorized version when using a supported type
|
||||
if (typeof(T) == typeof(byte) ||
|
||||
typeof(T) == typeof(sbyte) ||
|
||||
typeof(T) == typeof(bool))
|
||||
{
|
||||
ref T r0 = ref MemoryMarshal.GetReference(span);
|
||||
ref sbyte r1 = ref Unsafe.As<T, sbyte>(ref r0);
|
||||
int length = span.Length;
|
||||
sbyte target = Unsafe.As<T, sbyte>(ref value);
|
||||
|
||||
return Count(ref r1, length, target, sbyte.MaxValue);
|
||||
}
|
||||
|
||||
if (typeof(T) == typeof(char) ||
|
||||
typeof(T) == typeof(ushort) ||
|
||||
typeof(T) == typeof(short))
|
||||
{
|
||||
ref T r0 = ref MemoryMarshal.GetReference(span);
|
||||
ref short r1 = ref Unsafe.As<T, short>(ref r0);
|
||||
int length = span.Length;
|
||||
short target = Unsafe.As<T, short>(ref value);
|
||||
|
||||
return Count(ref r1, length, target, short.MaxValue);
|
||||
}
|
||||
|
||||
if (typeof(T) == typeof(int) ||
|
||||
typeof(T) == typeof(uint))
|
||||
{
|
||||
ref T r0 = ref MemoryMarshal.GetReference(span);
|
||||
ref int r1 = ref Unsafe.As<T, int>(ref r0);
|
||||
int length = span.Length;
|
||||
int target = Unsafe.As<T, int>(ref value);
|
||||
|
||||
return Count(ref r1, length, target, int.MaxValue);
|
||||
}
|
||||
|
||||
if (typeof(T) == typeof(long) ||
|
||||
typeof(T) == typeof(ulong))
|
||||
{
|
||||
ref T r0 = ref MemoryMarshal.GetReference(span);
|
||||
ref long r1 = ref Unsafe.As<T, long>(ref r0);
|
||||
int length = span.Length;
|
||||
long target = Unsafe.As<T, long>(ref value);
|
||||
|
||||
return Count(ref r1, length, target, int.MaxValue);
|
||||
}
|
||||
|
||||
return Count(ref MemoryMarshal.GetReference(span), span.Length, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Counts the number of occurrences of a given value into a target search space.
|
||||
/// </summary>
|
||||
/// <param name="r0">A <typeparamref name="T"/> reference to the start of the search space.</param>
|
||||
/// <param name="length">The number of items in the search space.</param>
|
||||
/// <param name="value">The <typeparamref name="T"/> value to look for.</param>
|
||||
/// <typeparam name="T">The type of value to look for.</typeparam>
|
||||
/// <returns>The number of occurrences of <paramref name="value"/> in the search space</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int Count<T>(ref T r0, int length, T value)
|
||||
where T : IEquatable<T>
|
||||
{
|
||||
int
|
||||
i = 0,
|
||||
result = 0,
|
||||
end8 = length - 8;
|
||||
|
||||
// Main loop with 8 unrolled iterations
|
||||
for (; i <= end8; i += 8)
|
||||
{
|
||||
result += Unsafe.Add(ref r0, i + 0).Equals(value).ToInt();
|
||||
result += Unsafe.Add(ref r0, i + 1).Equals(value).ToInt();
|
||||
result += Unsafe.Add(ref r0, i + 2).Equals(value).ToInt();
|
||||
result += Unsafe.Add(ref r0, i + 3).Equals(value).ToInt();
|
||||
result += Unsafe.Add(ref r0, i + 4).Equals(value).ToInt();
|
||||
result += Unsafe.Add(ref r0, i + 5).Equals(value).ToInt();
|
||||
result += Unsafe.Add(ref r0, i + 6).Equals(value).ToInt();
|
||||
result += Unsafe.Add(ref r0, i + 7).Equals(value).ToInt();
|
||||
}
|
||||
|
||||
// Iterate over the remaining values and count those that match
|
||||
for (; i < length; i++)
|
||||
{
|
||||
result += Unsafe.Add(ref r0, i).Equals(value).ToInt();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Counts the number of occurrences of a given value into a target search space.
|
||||
/// </summary>
|
||||
/// <param name="r0">A <typeparamref name="T"/> reference to the start of the search space.</param>
|
||||
/// <param name="length">The number of items in the search space.</param>
|
||||
/// <param name="value">The <typeparamref name="T"/> value to look for.</param>
|
||||
/// <param name="max">The maximum amount that a <typeparamref name="T"/> value can reach.</param>
|
||||
/// <typeparam name="T">The type of value to look for.</typeparam>
|
||||
/// <returns>The number of occurrences of <paramref name="value"/> in the search space</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int Count<T>(ref T r0, int length, T value, int max)
|
||||
where T : unmanaged, IEquatable<T>
|
||||
{
|
||||
int i = 0, result = 0;
|
||||
|
||||
// Only run the SIMD-enabled branch if the Vector<T> APIs are hardware accelerated
|
||||
if (Vector.IsHardwareAccelerated)
|
||||
{
|
||||
int end = length - Vector<T>.Count;
|
||||
|
||||
var partials = Vector<T>.Zero;
|
||||
var vc = new Vector<T>(value);
|
||||
|
||||
/* Run the fast path if the input span is short enough.
|
||||
* There are two branches here because if the search space is large
|
||||
* enough, the partial results could overflow if the target value
|
||||
* is present too many times. This upper limit depends on the type
|
||||
* being used, as the smaller the type is, the shorter the supported
|
||||
* fast range will be. In the worst case scenario, the same value appears
|
||||
* always at the offset aligned with the same SIMD value in the current
|
||||
* register. Therefore, if the input span is longer than that minimum
|
||||
* threshold, additional checks need to be performed to avoid overflows.
|
||||
* This value is equal to the maximum (signed) numerical value for the current
|
||||
* type, divided by the number of value that can fit in a register, minus 1.
|
||||
* This is because the partial results are accumulated with a dot product,
|
||||
* which sums them horizontally while still working on the original type.
|
||||
* Dividing the max value by their count ensures that overflows can't happen.
|
||||
* The check is moved outside of the loop to enable a branchless version
|
||||
* of this method if the input span is guaranteed not to cause overflows.
|
||||
* Otherwise, the safe but slower variant is used. */
|
||||
int threshold = (max / Vector<T>.Count) - 1;
|
||||
|
||||
if (length <= threshold)
|
||||
{
|
||||
for (; i <= end; i += Vector<T>.Count)
|
||||
{
|
||||
ref T ri = ref Unsafe.Add(ref r0, i);
|
||||
|
||||
/* Load the current Vector<T> register, and then use
|
||||
* Vector.Equals to check for matches. This API sets the
|
||||
* values corresponding to matching pairs to all 1s.
|
||||
* Since the input type is guaranteed to always be signed,
|
||||
* this means that a value with all 1s represents -1, as
|
||||
* signed numbers are represented in two's complement.
|
||||
* So we can subtract this intermediate value to the
|
||||
* partial results, which effectively sums 1 for each match. */
|
||||
var vi = Unsafe.As<T, Vector<T>>(ref ri);
|
||||
var ve = Vector.Equals(vi, vc);
|
||||
|
||||
partials -= ve;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int j = 0; i <= end; i += Vector<T>.Count, j++)
|
||||
{
|
||||
ref T ri = ref Unsafe.Add(ref r0, i);
|
||||
|
||||
// Same as before
|
||||
var vi = Unsafe.As<T, Vector<T>>(ref ri);
|
||||
var ve = Vector.Equals(vi, vc);
|
||||
|
||||
partials -= ve;
|
||||
|
||||
// Additional checks to avoid overflows
|
||||
if (j == threshold)
|
||||
{
|
||||
j = 0;
|
||||
result += CastToInt(Vector.Dot(partials, Vector<T>.One));
|
||||
partials = Vector<T>.Zero;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Compute the horizontal sum of the partial results
|
||||
result += CastToInt(Vector.Dot(partials, Vector<T>.One));
|
||||
}
|
||||
|
||||
// Iterate over the remaining values and count those that match
|
||||
for (; i < length; i++)
|
||||
{
|
||||
result += Unsafe.Add(ref r0, i).Equals(value).ToInt();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Casts a value of a given type to <see cref="int"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The input type to cast.</typeparam>
|
||||
/// <param name="value">The input <typeparamref name="T"/> value to cast to <see cref="int"/>.</param>
|
||||
/// <returns>The <see cref="int"/> cast of <paramref name="value"/>.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int CastToInt<T>(T value)
|
||||
where T : unmanaged
|
||||
{
|
||||
if (typeof(T) == typeof(sbyte))
|
||||
{
|
||||
return Unsafe.As<T, sbyte>(ref value);
|
||||
}
|
||||
|
||||
if (typeof(T) == typeof(short))
|
||||
{
|
||||
return Unsafe.As<T, short>(ref value);
|
||||
}
|
||||
|
||||
if (typeof(T) == typeof(int))
|
||||
{
|
||||
return Unsafe.As<T, int>(ref value);
|
||||
}
|
||||
|
||||
if (typeof(T) == typeof(long))
|
||||
{
|
||||
return (int)Unsafe.As<T, long>(ref value);
|
||||
}
|
||||
|
||||
throw new ArgumentException($"Invalid input type {typeof(T)}", nameof(value));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,15 +7,14 @@ using System.Diagnostics.Contracts;
|
|||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Toolkit.HighPerformance.Enumerables;
|
||||
|
||||
#nullable enable
|
||||
using Microsoft.Toolkit.HighPerformance.Helpers.Internals;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers for working with the <see cref="ReadOnlySpan{T}"/> type.
|
||||
/// </summary>
|
||||
public static partial class ReadOnlySpanExtensions
|
||||
public static class ReadOnlySpanExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns a reference to the first element within a given <see cref="ReadOnlySpan{T}"/>, with no bounds checks.
|
||||
|
@ -49,11 +48,137 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
return ref ri;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a reference to the first element within a given <see cref="ReadOnlySpan{T}"/>, clamping the input index in the valid range.
|
||||
/// If the <paramref name="i"/> parameter exceeds the length of <paramref name="span"/>, it will be clamped to 0.
|
||||
/// Therefore, the returned reference will always point to a valid element within <paramref name="span"/>, assuming it is not empty.
|
||||
/// This method is specifically meant to efficiently index lookup tables, especially if they point to constant data.
|
||||
/// Consider this example where a lookup table is used to validate whether a given character is within a specific set:
|
||||
/// <code>
|
||||
/// public static ReadOnlySpan<bool> ValidSetLookupTable => new bool[]
|
||||
/// {
|
||||
/// false, true, true, true, true, true, false, true,
|
||||
/// false, false, true, false, true, false, true, false,
|
||||
/// true, false, false, true, false, false, false, false,
|
||||
/// false, false, false, false, true, true, false, true
|
||||
/// };
|
||||
///
|
||||
/// int ch = Console.Read();
|
||||
/// bool isValid = ValidSetLookupTable.DangerousGetLookupReference(ch);
|
||||
/// </code>
|
||||
/// Even if the input index is outside the range of the lookup table, being clamped to 0, it will
|
||||
/// just cause the value 0 to be returned in this case, which is functionally the same for the check
|
||||
/// being performed. This extension can easily be used whenever the first position in a lookup
|
||||
/// table being referenced corresponds to a falsey value, like in this case.
|
||||
/// Additionally, the example above leverages a compiler optimization introduced with C# 7.3,
|
||||
/// which allows <see cref="ReadOnlySpan{T}"/> instances pointing to compile-time constant data
|
||||
/// to be directly mapped to the static .text section in the final assembly: the array being
|
||||
/// created in code will never actually be allocated, and the <see cref="ReadOnlySpan{T}"/> will
|
||||
/// just point to constant data. Note that this only works for blittable values that are not
|
||||
/// dependent on the byte endianness of the system, like <see cref="byte"/> or <see cref="bool"/>.
|
||||
/// For more info, see <see href="https://vcsjones.dev/2019/02/01/csharp-readonly-span-bytes-static/"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of elements in the input <see cref="ReadOnlySpan{T}"/> instance.</typeparam>
|
||||
/// <param name="span">The input <see cref="ReadOnlySpan{T}"/> instance.</param>
|
||||
/// <param name="i">The index of the element to retrieve within <paramref name="span"/>.</param>
|
||||
/// <returns>
|
||||
/// A reference to the element within <paramref name="span"/> at the index specified by <paramref name="i"/>,
|
||||
/// or a reference to the first element within <paramref name="span"/> if <paramref name="i"/> was not a valid index.
|
||||
/// </returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ref readonly T DangerousGetLookupReferenceAt<T>(this ReadOnlySpan<T> span, int i)
|
||||
{
|
||||
// Check whether the input is in range by first casting both
|
||||
// operands to uint and then comparing them, as this allows
|
||||
// the test to also identify cases where the input index is
|
||||
// less than zero. The resulting bool is then reinterpreted
|
||||
// as a byte (either 1 or 0), and then decremented.
|
||||
// This will result in either 0 if the input index was
|
||||
// valid for the target span, or -1 (0xFFFFFFFF) otherwise.
|
||||
// The result is then negated, producing the value 0xFFFFFFFF
|
||||
// for valid indices, or 0 otherwise. The generated mask
|
||||
// is then combined with the original index. This leaves
|
||||
// the index intact if it was valid, otherwise zeroes it.
|
||||
// The computed offset is finally used to access the
|
||||
// lookup table, and it is guaranteed to never go out of
|
||||
// bounds unless the input span was just empty, which for a
|
||||
// lookup table can just be assumed to always be false.
|
||||
bool isInRange = (uint)i < (uint)span.Length;
|
||||
byte rangeFlag = Unsafe.As<bool, byte>(ref isInRange);
|
||||
int
|
||||
negativeFlag = rangeFlag - 1,
|
||||
mask = ~negativeFlag,
|
||||
offset = i & mask;
|
||||
ref T r0 = ref MemoryMarshal.GetReference(span);
|
||||
ref T r1 = ref Unsafe.Add(ref r0, offset);
|
||||
|
||||
return ref r1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the index of an element of a given <see cref="ReadOnlySpan{T}"/> from its reference.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type if items in the input <see cref="ReadOnlySpan{T}"/>.</typeparam>
|
||||
/// <param name="span">The input <see cref="ReadOnlySpan{T}"/> to calculate the index for.</param>
|
||||
/// <param name="value">The reference to the target item to get the index for.</param>
|
||||
/// <returns>The index of <paramref name="value"/> within <paramref name="span"/>.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="value"/> does not belong to <paramref name="span"/>.</exception>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe int IndexOf<T>(this ReadOnlySpan<T> span, in T value)
|
||||
{
|
||||
ref T r0 = ref MemoryMarshal.GetReference(span);
|
||||
ref T r1 = ref Unsafe.AsRef(value);
|
||||
IntPtr byteOffset = Unsafe.ByteOffset(ref r0, ref r1);
|
||||
|
||||
if (sizeof(IntPtr) == sizeof(long))
|
||||
{
|
||||
long elementOffset = (long)byteOffset / Unsafe.SizeOf<T>();
|
||||
|
||||
if ((ulong)elementOffset >= (ulong)span.Length)
|
||||
{
|
||||
SpanExtensions.ThrowArgumentOutOfRangeExceptionForInvalidReference();
|
||||
}
|
||||
|
||||
return unchecked((int)elementOffset);
|
||||
}
|
||||
else
|
||||
{
|
||||
int elementOffset = (int)byteOffset / Unsafe.SizeOf<T>();
|
||||
|
||||
if ((uint)elementOffset >= (uint)span.Length)
|
||||
{
|
||||
SpanExtensions.ThrowArgumentOutOfRangeExceptionForInvalidReference();
|
||||
}
|
||||
|
||||
return elementOffset;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Counts the number of occurrences of a given value into a target <see cref="ReadOnlySpan{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items in the input <see cref="ReadOnlySpan{T}"/> instance.</typeparam>
|
||||
/// <param name="span">The input <see cref="ReadOnlySpan{T}"/> instance to read.</param>
|
||||
/// <param name="value">The <typeparamref name="T"/> value to look for.</param>
|
||||
/// <returns>The number of occurrences of <paramref name="value"/> in <paramref name="span"/>.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static int Count<T>(this ReadOnlySpan<T> span, T value)
|
||||
where T : IEquatable<T>
|
||||
{
|
||||
ref T r0 = ref MemoryMarshal.GetReference(span);
|
||||
IntPtr length = (IntPtr)span.Length;
|
||||
|
||||
return SpanHelper.Count(ref r0, length, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Casts a <see cref="ReadOnlySpan{T}"/> of one primitive type <typeparamref name="T"/> to <see cref="ReadOnlySpan{T}"/> of bytes.
|
||||
/// That type may not contain pointers or references. This is checked at runtime in order to preserve type safety.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type if items in the source <see cref="ReadOnlySpan{T}"/></typeparam>
|
||||
/// <typeparam name="T">The type if items in the source <see cref="ReadOnlySpan{T}"/>.</typeparam>
|
||||
/// <param name="span">The source slice, of type <typeparamref name="T"/>.</param>
|
||||
/// <returns>A <see cref="ReadOnlySpan{T}"/> of bytes.</returns>
|
||||
/// <exception cref="ArgumentException">
|
||||
|
@ -147,42 +272,32 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
|
||||
/// <summary>
|
||||
/// Gets a content hash from the input <see cref="ReadOnlySpan{T}"/> instance using the Djb2 algorithm.
|
||||
/// It was designed by <see href="https://en.wikipedia.org/wiki/Daniel_J._Bernstein">Daniel J. Bernstein</see> and is a
|
||||
/// <see href="https://en.wikipedia.org/wiki/List_of_hash_functions#Non-cryptographic_hash_functions">non-cryptographic has function</see>.
|
||||
/// The main advantages of this algorithm are a good distribution of the resulting hash codes, which results in a relatively low
|
||||
/// number of collisions, while at the same time being particularly fast to process, making it suitable for quickly hashing
|
||||
/// even long sequences of values. For the reference implementation, see: <see href="http://www.cse.yorku.ca/~oz/hash.html"/>.
|
||||
/// For details on the used constants, see the details provided in this StackOverflow answer (as well as the accepted one):
|
||||
/// <see href="https://stackoverflow.com/questions/10696223/reason-for-5381-number-in-djb-hash-function/13809282#13809282"/>.
|
||||
/// Additionally, a comparison between some common hashing algoriths can be found in the reply to this StackExchange question:
|
||||
/// <see href="https://softwareengineering.stackexchange.com/questions/49550/which-hashing-algorithm-is-best-for-uniqueness-and-speed"/>.
|
||||
/// Note that the exact implementation is slightly different in this method when it is not called on a sequence of <see cref="byte"/>
|
||||
/// values: in this case the <see cref="object.GetHashCode"/> method will be invoked for each <typeparamref name="T"/> value in
|
||||
/// the provided <see cref="ReadOnlySpan{T}"/> instance, and then those values will be combined using the Djb2 algorithm.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items in the input <see cref="ReadOnlySpan{T}"/> instance.</typeparam>
|
||||
/// <param name="span">The input <see cref="ReadOnlySpan{T}"/> instance.</param>
|
||||
/// <returns>The Djb2 value for the input <see cref="ReadOnlySpan{T}"/> instance.</returns>
|
||||
/// <remarks>The Djb2 hash is fully deterministic and with no random components.</remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int GetDjb2HashCode<T>(this ReadOnlySpan<T> span)
|
||||
where T : notnull
|
||||
{
|
||||
ref T r0 = ref MemoryMarshal.GetReference(span);
|
||||
int
|
||||
hash = 5381,
|
||||
length = span.Length,
|
||||
i = 0,
|
||||
end8 = length - 8;
|
||||
IntPtr length = (IntPtr)span.Length;
|
||||
|
||||
// Main loop with 8 unrolled iterations
|
||||
for (; i <= end8; i += 8)
|
||||
{
|
||||
hash = unchecked((hash << 5) + hash + Unsafe.Add(ref r0, i + 0).GetHashCode());
|
||||
hash = unchecked((hash << 5) + hash + Unsafe.Add(ref r0, i + 1).GetHashCode());
|
||||
hash = unchecked((hash << 5) + hash + Unsafe.Add(ref r0, i + 2).GetHashCode());
|
||||
hash = unchecked((hash << 5) + hash + Unsafe.Add(ref r0, i + 3).GetHashCode());
|
||||
hash = unchecked((hash << 5) + hash + Unsafe.Add(ref r0, i + 4).GetHashCode());
|
||||
hash = unchecked((hash << 5) + hash + Unsafe.Add(ref r0, i + 5).GetHashCode());
|
||||
hash = unchecked((hash << 5) + hash + Unsafe.Add(ref r0, i + 6).GetHashCode());
|
||||
hash = unchecked((hash << 5) + hash + Unsafe.Add(ref r0, i + 7).GetHashCode());
|
||||
}
|
||||
|
||||
// Handle the leftover items
|
||||
for (; i < length; i++)
|
||||
{
|
||||
hash = unchecked((hash << 5) + hash + Unsafe.Add(ref r0, i).GetHashCode());
|
||||
}
|
||||
|
||||
return hash;
|
||||
return SpanHelper.GetDjb2HashCode(ref r0, length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ using System.Diagnostics.Contracts;
|
|||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Toolkit.HighPerformance.Enumerables;
|
||||
using Microsoft.Toolkit.HighPerformance.Helpers.Internals;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Extensions
|
||||
{
|
||||
|
@ -51,7 +52,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
/// Casts a <see cref="Span{T}"/> of one primitive type <typeparamref name="T"/> to <see cref="Span{T}"/> of bytes.
|
||||
/// That type may not contain pointers or references. This is checked at runtime in order to preserve type safety.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type if items in the source <see cref="Span{T}"/></typeparam>
|
||||
/// <typeparam name="T">The type if items in the source <see cref="Span{T}"/>.</typeparam>
|
||||
/// <param name="span">The source slice, of type <typeparamref name="T"/>.</param>
|
||||
/// <returns>A <see cref="Span{T}"/> of bytes.</returns>
|
||||
/// <exception cref="ArgumentException">
|
||||
|
@ -91,6 +92,45 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
return MemoryMarshal.Cast<TFrom, TTo>(span);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the index of an element of a given <see cref="Span{T}"/> from its reference.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type if items in the input <see cref="Span{T}"/>.</typeparam>
|
||||
/// <param name="span">The input <see cref="Span{T}"/> to calculate the index for.</param>
|
||||
/// <param name="value">The reference to the target item to get the index for.</param>
|
||||
/// <returns>The index of <paramref name="value"/> within <paramref name="span"/>.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="value"/> does not belong to <paramref name="span"/>.</exception>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe int IndexOf<T>(this Span<T> span, ref T value)
|
||||
{
|
||||
ref T r0 = ref MemoryMarshal.GetReference(span);
|
||||
IntPtr byteOffset = Unsafe.ByteOffset(ref r0, ref value);
|
||||
|
||||
if (sizeof(IntPtr) == sizeof(long))
|
||||
{
|
||||
long elementOffset = (long)byteOffset / Unsafe.SizeOf<T>();
|
||||
|
||||
if ((ulong)elementOffset >= (ulong)span.Length)
|
||||
{
|
||||
ThrowArgumentOutOfRangeExceptionForInvalidReference();
|
||||
}
|
||||
|
||||
return unchecked((int)elementOffset);
|
||||
}
|
||||
else
|
||||
{
|
||||
int elementOffset = (int)byteOffset / Unsafe.SizeOf<T>();
|
||||
|
||||
if ((uint)elementOffset >= (uint)span.Length)
|
||||
{
|
||||
ThrowArgumentOutOfRangeExceptionForInvalidReference();
|
||||
}
|
||||
|
||||
return elementOffset;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Counts the number of occurrences of a given value into a target <see cref="Span{T}"/> instance.
|
||||
/// </summary>
|
||||
|
@ -103,7 +143,10 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
public static int Count<T>(this Span<T> span, T value)
|
||||
where T : IEquatable<T>
|
||||
{
|
||||
return ReadOnlySpanExtensions.Count(span, value);
|
||||
ref T r0 = ref MemoryMarshal.GetReference(span);
|
||||
IntPtr length = (IntPtr)span.Length;
|
||||
|
||||
return SpanHelper.Count(ref r0, length, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -160,6 +203,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
|
||||
/// <summary>
|
||||
/// Gets a content hash from the input <see cref="Span{T}"/> instance using the Djb2 algorithm.
|
||||
/// For more info, see the documentation for <see cref="ReadOnlySpanExtensions.GetDjb2HashCode{T}"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items in the input <see cref="Span{T}"/> instance.</typeparam>
|
||||
/// <param name="span">The input <see cref="Span{T}"/> instance.</param>
|
||||
|
@ -170,7 +214,19 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
public static int GetDjb2HashCode<T>(this Span<T> span)
|
||||
where T : notnull
|
||||
{
|
||||
return ReadOnlySpanExtensions.GetDjb2HashCode<T>(span);
|
||||
ref T r0 = ref MemoryMarshal.GetReference(span);
|
||||
IntPtr length = (IntPtr)span.Length;
|
||||
|
||||
return SpanHelper.GetDjb2HashCode(ref r0, length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the given reference is out of range.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
internal static void ThrowArgumentOutOfRangeExceptionForInvalidReference()
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("value", "The input reference does not belong to an element of the input span");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
|
||||
|
@ -39,9 +38,8 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
/// <summary>
|
||||
/// A <see langword="struct"/> that is used to enter and hold a <see cref="SpinLock"/> through a <see langword="using"/> block or statement.
|
||||
/// </summary>
|
||||
[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1206", Justification = "The type is a ref struct")]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public unsafe ref struct UnsafeLock
|
||||
public readonly unsafe ref struct UnsafeLock
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="SpinLock"/>* pointer to the target <see cref="SpinLock"/> value to use.
|
||||
|
@ -79,7 +77,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
}
|
||||
}
|
||||
|
||||
#if NETSTANDARD2_1
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
/// <summary>
|
||||
/// Enters a specified <see cref="SpinLock"/> instance and returns a wrapper to use to release the lock.
|
||||
/// This extension should be used though a <see langword="using"/> block or statement:
|
||||
|
@ -132,9 +130,8 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
/// <summary>
|
||||
/// A <see langword="struct"/> that is used to enter and hold a <see cref="SpinLock"/> through a <see langword="using"/> block or statement.
|
||||
/// </summary>
|
||||
[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1206", Justification = "The type is a ref struct")]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public ref struct Lock
|
||||
public readonly ref struct Lock
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="Ref{T}"/> instance pointing to the target <see cref="SpinLock"/> value to use.
|
||||
|
@ -146,7 +143,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
/// </summary>
|
||||
private readonly bool lockTaken;
|
||||
|
||||
#if NETSTANDARD2_1
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Lock"/> struct.
|
||||
/// </summary>
|
||||
|
|
|
@ -0,0 +1,254 @@
|
|||
// 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.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
#if !SPAN_RUNTIME_SUPPORT
|
||||
using System.Buffers;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
#endif
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers for working with the <see cref="Stream"/> type.
|
||||
/// </summary>
|
||||
public static class StreamExtensions
|
||||
{
|
||||
#if !SPAN_RUNTIME_SUPPORT
|
||||
/// <summary>
|
||||
/// Asynchronously reads a sequence of bytes from a given <see cref="Stream"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="stream">The source <see cref="Stream"/> to read data from.</param>
|
||||
/// <param name="buffer">The destination <see cref="Memory{T}"/> to write data to.</param>
|
||||
/// <param name="cancellationToken">The optional <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="ValueTask"/> representing the operation being performed.</returns>
|
||||
public static ValueTask<int> ReadAsync(this Stream stream, Memory<byte> buffer, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return new ValueTask<int>(Task.FromCanceled<int>(cancellationToken));
|
||||
}
|
||||
|
||||
// If the memory wraps an array, extract it and use it directly
|
||||
if (MemoryMarshal.TryGetArray(buffer, out ArraySegment<byte> segment))
|
||||
{
|
||||
return new ValueTask<int>(stream.ReadAsync(segment.Array!, segment.Offset, segment.Count, cancellationToken));
|
||||
}
|
||||
|
||||
// Local function used as the fallback path. This happens when the input memory
|
||||
// doesn't wrap an array instance we can use. We use a local function as we need
|
||||
// the body to be asynchronous, in order to execute the finally block after the
|
||||
// write operation has been completed. By separating the logic, we can keep the
|
||||
// main method as a synchronous, value-task returning function. This fallback
|
||||
// path should hopefully be pretty rare, as memory instances are typically just
|
||||
// created around arrays, often being rented from a memory pool in particular.
|
||||
static async Task<int> ReadAsyncFallback(Stream stream, Memory<byte> buffer, CancellationToken cancellationToken)
|
||||
{
|
||||
byte[] rent = ArrayPool<byte>.Shared.Rent(buffer.Length);
|
||||
|
||||
try
|
||||
{
|
||||
int bytesRead = await stream.ReadAsync(rent, 0, buffer.Length, cancellationToken);
|
||||
|
||||
if (bytesRead > 0)
|
||||
{
|
||||
rent.AsSpan(0, bytesRead).CopyTo(buffer.Span);
|
||||
}
|
||||
|
||||
return bytesRead;
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(rent);
|
||||
}
|
||||
}
|
||||
|
||||
return new ValueTask<int>(ReadAsyncFallback(stream, buffer, cancellationToken));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously writes a sequence of bytes to a given <see cref="Stream"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="stream">The destination <see cref="Stream"/> to write data to.</param>
|
||||
/// <param name="buffer">The source <see cref="ReadOnlyMemory{T}"/> to read data from.</param>
|
||||
/// <param name="cancellationToken">The optional <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="ValueTask"/> representing the operation being performed.</returns>
|
||||
public static ValueTask WriteAsync(this Stream stream, ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return new ValueTask(Task.FromCanceled(cancellationToken));
|
||||
}
|
||||
|
||||
if (MemoryMarshal.TryGetArray(buffer, out ArraySegment<byte> segment))
|
||||
{
|
||||
return new ValueTask(stream.WriteAsync(segment.Array!, segment.Offset, segment.Count, cancellationToken));
|
||||
}
|
||||
|
||||
// Local function, same idea as above
|
||||
static async Task WriteAsyncFallback(Stream stream, ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken)
|
||||
{
|
||||
byte[] rent = ArrayPool<byte>.Shared.Rent(buffer.Length);
|
||||
|
||||
try
|
||||
{
|
||||
buffer.Span.CopyTo(rent);
|
||||
|
||||
await stream.WriteAsync(rent, 0, buffer.Length, cancellationToken);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(rent);
|
||||
}
|
||||
}
|
||||
|
||||
return new ValueTask(WriteAsyncFallback(stream, buffer, cancellationToken));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a sequence of bytes from a given <see cref="Stream"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="stream">The source <see cref="Stream"/> to read data from.</param>
|
||||
/// <param name="buffer">The target <see cref="Span{T}"/> to write data to.</param>
|
||||
/// <returns>The number of bytes that have been read.</returns>
|
||||
public static int Read(this Stream stream, Span<byte> buffer)
|
||||
{
|
||||
byte[] rent = ArrayPool<byte>.Shared.Rent(buffer.Length);
|
||||
|
||||
try
|
||||
{
|
||||
int bytesRead = stream.Read(rent, 0, buffer.Length);
|
||||
|
||||
if (bytesRead > 0)
|
||||
{
|
||||
rent.AsSpan(0, bytesRead).CopyTo(buffer);
|
||||
}
|
||||
|
||||
return bytesRead;
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(rent);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a sequence of bytes to a given <see cref="Stream"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="stream">The destination <see cref="Stream"/> to write data to.</param>
|
||||
/// <param name="buffer">The source <see cref="Span{T}"/> to read data from.</param>
|
||||
public static void Write(this Stream stream, ReadOnlySpan<byte> buffer)
|
||||
{
|
||||
byte[] rent = ArrayPool<byte>.Shared.Rent(buffer.Length);
|
||||
|
||||
try
|
||||
{
|
||||
buffer.CopyTo(rent);
|
||||
|
||||
stream.Write(rent, 0, buffer.Length);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(rent);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Reads a value of a specified type from a source <see cref="Stream"/> instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of value to read.</typeparam>
|
||||
/// <param name="stream">The source <see cref="Stream"/> instance to read from.</param>
|
||||
/// <returns>The <typeparamref name="T"/> value read from <paramref name="stream"/>.</returns>
|
||||
/// <exception cref="InvalidOperationException">Thrown if <paramref name="stream"/> reaches the end.</exception>
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
// Avoid inlining as we're renting a stack buffer, which
|
||||
// cause issues if this method was called inside a loop
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
#endif
|
||||
public static T Read<T>(this Stream stream)
|
||||
where T : unmanaged
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
Span<byte> span = stackalloc byte[Unsafe.SizeOf<T>()];
|
||||
|
||||
if (stream.Read(span) != span.Length)
|
||||
{
|
||||
ThrowInvalidOperationExceptionForEndOfStream();
|
||||
}
|
||||
|
||||
ref byte r0 = ref MemoryMarshal.GetReference(span);
|
||||
|
||||
return Unsafe.ReadUnaligned<T>(ref r0);
|
||||
#else
|
||||
int length = Unsafe.SizeOf<T>();
|
||||
byte[] buffer = ArrayPool<byte>.Shared.Rent(length);
|
||||
|
||||
try
|
||||
{
|
||||
if (stream.Read(buffer, 0, length) != length)
|
||||
{
|
||||
ThrowInvalidOperationExceptionForEndOfStream();
|
||||
}
|
||||
|
||||
return Unsafe.ReadUnaligned<T>(ref buffer[0]);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(buffer);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a value of a specified type into a target <see cref="Stream"/> instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of value to write.</typeparam>
|
||||
/// <param name="stream">The target <see cref="Stream"/> instance to write to.</param>
|
||||
/// <param name="value">The input value to write to <paramref name="stream"/>.</param>
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
#endif
|
||||
public static void Write<T>(this Stream stream, in T value)
|
||||
where T : unmanaged
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
ref T r0 = ref Unsafe.AsRef(value);
|
||||
ref byte r1 = ref Unsafe.As<T, byte>(ref r0);
|
||||
int length = Unsafe.SizeOf<T>();
|
||||
|
||||
ReadOnlySpan<byte> span = MemoryMarshal.CreateReadOnlySpan(ref r1, length);
|
||||
|
||||
stream.Write(span);
|
||||
#else
|
||||
int length = Unsafe.SizeOf<T>();
|
||||
byte[] buffer = ArrayPool<byte>.Shared.Rent(length);
|
||||
|
||||
try
|
||||
{
|
||||
Unsafe.WriteUnaligned(ref buffer[0], value);
|
||||
|
||||
stream.Write(buffer, 0, length);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(buffer);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="InvalidOperationException"/> when <see cref="Read{T}"/> fails.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void ThrowInvalidOperationExceptionForEndOfStream()
|
||||
{
|
||||
throw new InvalidOperationException("The stream didn't contain enough data to read the requested item");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,6 +7,7 @@ using System.Diagnostics.Contracts;
|
|||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Toolkit.HighPerformance.Enumerables;
|
||||
using Microsoft.Toolkit.HighPerformance.Helpers.Internals;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Extensions
|
||||
{
|
||||
|
@ -25,9 +26,15 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ref char DangerousGetReference(this string text)
|
||||
{
|
||||
#if NETCOREAPP3_1
|
||||
return ref Unsafe.AsRef(text.GetPinnableReference());
|
||||
#elif NETCOREAPP2_1
|
||||
var stringData = Unsafe.As<RawStringData>(text);
|
||||
|
||||
return ref stringData.Data;
|
||||
#else
|
||||
return ref MemoryMarshal.GetReference(text.AsSpan());
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -41,12 +48,19 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ref char DangerousGetReferenceAt(this string text, int i)
|
||||
{
|
||||
var stringData = Unsafe.As<RawStringData>(text);
|
||||
ref var ri = ref Unsafe.Add(ref stringData.Data, i);
|
||||
#if NETCOREAPP3_1
|
||||
ref char r0 = ref Unsafe.AsRef(text.GetPinnableReference());
|
||||
#elif NETCOREAPP2_1
|
||||
ref char r0 = ref Unsafe.As<RawStringData>(text).Data;
|
||||
#else
|
||||
ref char r0 = ref MemoryMarshal.GetReference(text.AsSpan());
|
||||
#endif
|
||||
ref char ri = ref Unsafe.Add(ref r0, i);
|
||||
|
||||
return ref ri;
|
||||
}
|
||||
|
||||
#if NETCOREAPP2_1
|
||||
// Description adapted from CoreCLR: see https://source.dot.net/#System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs,285.
|
||||
// CLR strings are laid out in memory as follows:
|
||||
// [ sync block || pMethodTable || length || string data .. ]
|
||||
|
@ -65,6 +79,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
#pragma warning restore CS0649
|
||||
#pragma warning restore SA1401
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Counts the number of occurrences of a given character into a target <see cref="string"/> instance.
|
||||
|
@ -76,7 +91,10 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int Count(this string text, char c)
|
||||
{
|
||||
return text.AsSpan().Count(c);
|
||||
ref char r0 = ref text.DangerousGetReference();
|
||||
IntPtr length = (IntPtr)text.Length;
|
||||
|
||||
return SpanHelper.Count(ref r0, length, c);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -130,6 +148,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
|
||||
/// <summary>
|
||||
/// Gets a content hash from the input <see cref="string"/> instance using the Djb2 algorithm.
|
||||
/// For more info, see the documentation for <see cref="ReadOnlySpanExtensions.GetDjb2HashCode{T}"/>.
|
||||
/// </summary>
|
||||
/// <param name="text">The source <see cref="string"/> to enumerate.</param>
|
||||
/// <returns>The Djb2 value for the input <see cref="string"/> instance.</returns>
|
||||
|
@ -138,7 +157,10 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int GetDjb2HashCode(this string text)
|
||||
{
|
||||
return text.AsSpan().GetDjb2HashCode();
|
||||
ref char r0 = ref text.DangerousGetReference();
|
||||
IntPtr length = (IntPtr)text.Length;
|
||||
|
||||
return SpanHelper.GetDjb2HashCode(ref r0, length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,9 @@
|
|||
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
#if NETCOREAPP3_1
|
||||
using System.Runtime.Intrinsics.X86;
|
||||
#endif
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Helpers
|
||||
{
|
||||
|
@ -16,7 +19,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
|
|||
/// Checks whether or not a given bit is set.
|
||||
/// </summary>
|
||||
/// <param name="value">The input <see cref="uint"/> value.</param>
|
||||
/// <param name="n">The position of the bit to check.</param>
|
||||
/// <param name="n">The position of the bit to check (in [0, 31] range).</param>
|
||||
/// <returns>Whether or not the n-th bit is set.</returns>
|
||||
/// <remarks>
|
||||
/// This method doesn't validate <paramref name="n"/> against the valid range.
|
||||
|
@ -30,21 +33,81 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
|
|||
// Read the n-th bit, downcast to byte
|
||||
byte flag = (byte)((value >> n) & 1);
|
||||
|
||||
/* Reinterpret the byte to avoid the test, setnz and
|
||||
* movzx instructions (asm x64). This is because the JIT
|
||||
* compiler is able to optimize this reinterpret-cast as
|
||||
* a single "and eax, 0x1" instruction, whereas if we had
|
||||
* compared the previous computed flag against 0, the assembly
|
||||
* would have had to perform the test, set the non-zero
|
||||
* flag and then extend the (byte) result to eax. */
|
||||
// Reinterpret the byte to avoid the test, setnz and
|
||||
// movzx instructions (asm x64). This is because the JIT
|
||||
// compiler is able to optimize this reinterpret-cast as
|
||||
// a single "and eax, 0x1" instruction, whereas if we had
|
||||
// compared the previous computed flag against 0, the assembly
|
||||
// would have had to perform the test, set the non-zero
|
||||
// flag and then extend the (byte) result to eax.
|
||||
return Unsafe.As<byte, bool>(ref flag);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether or not a given bit is set in a given bitwise lookup table.
|
||||
/// This method provides a branchless, register-based (with no memory accesses) way to
|
||||
/// check whether a given value is valid, according to a precomputed lookup table.
|
||||
/// It is similar in behavior to <see cref="HasFlag(uint,int)"/>, with the main difference
|
||||
/// being that this method will also validate the input <paramref name="x"/> parameter, and
|
||||
/// will always return <see langword="false"/> if it falls outside of the expected interval.
|
||||
/// Additionally, this method accepts a <paramref name="min"/> parameter, which is used to
|
||||
/// decrement the input parameter <paramref name="x"/> to ensure that the range of accepted
|
||||
/// values fits within the available 32 bits of the lookup table in use.
|
||||
/// For more info on this optimization technique, see <see href="https://egorbo.com/llvm-range-checks.html"/>.
|
||||
/// Here is how the code from the lik above would be implemented using this method:
|
||||
/// <code>
|
||||
/// bool IsReservedCharacter(char c)
|
||||
/// {
|
||||
/// return BitHelper.HasLookupFlag(314575237u, c, 36);
|
||||
/// }
|
||||
/// </code>
|
||||
/// The resulted assembly is virtually identical, with the added optimization that the one
|
||||
/// produced by <see cref="HasLookupFlag(uint,int,int)"/> has no conditional branches at all.
|
||||
/// </summary>
|
||||
/// <param name="table">The input lookup table to use.</param>
|
||||
/// <param name="x">The input value to check.</param>
|
||||
/// <param name="min">The minimum accepted value for <paramref name="x"/> (defaults to 0).</param>
|
||||
/// <returns>Whether or not the corresponding flag for <paramref name="x"/> is set in <paramref name="table"/>.</returns>
|
||||
/// <remarks>
|
||||
/// For best results, as shown in the sample code, both <paramref name="table"/> and <paramref name="min"/>
|
||||
/// should be compile-time constants, so that the JIT compiler will be able to produce more efficient code.
|
||||
/// </remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool HasLookupFlag(uint table, int x, int min = 0)
|
||||
{
|
||||
// First, the input value is scaled down by the given minimum.
|
||||
// This step will be skipped entirely if min is just the default of 0.
|
||||
// The valid range is given by 32, which is the number of bits in the
|
||||
// lookup table. The input value is first cast to uint so that if it was
|
||||
// negative, the check will fail as well. Then, the result of this
|
||||
// operation is used to compute a bitwise flag of either 0xFFFFFFFF if the
|
||||
// input is accepted, or all 0 otherwise. The target bit is then extracted,
|
||||
// and this value is combined with the previous mask. This is done so that
|
||||
// if the shift was performed with a value that was too high, which has an
|
||||
// undefined behavior and could produce a non-0 value, the mask will reset
|
||||
// the final value anyway. This result is then unchecked-cast to a byte (as
|
||||
// it is guaranteed to always be either 1 or 0), and then reinterpreted
|
||||
// as a bool just like in the HasFlag method above, and then returned.
|
||||
int i = x - min;
|
||||
bool isInRange = (uint)i < 32u;
|
||||
byte byteFlag = Unsafe.As<bool, byte>(ref isInRange);
|
||||
int
|
||||
negativeFlag = byteFlag - 1,
|
||||
mask = ~negativeFlag,
|
||||
shift = unchecked((int)((table >> i) & 1)),
|
||||
and = shift & mask;
|
||||
byte result = unchecked((byte)and);
|
||||
bool valid = Unsafe.As<byte, bool>(ref result);
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a bit to a specified value.
|
||||
/// </summary>
|
||||
/// <param name="value">The target <see cref="uint"/> value.</param>
|
||||
/// <param name="n">The position of the bit to set or clear.</param>
|
||||
/// <param name="n">The position of the bit to set or clear (in [0, 31] range).</param>
|
||||
/// <param name="flag">The value to assign to the target bit.</param>
|
||||
/// <remarks>
|
||||
/// Just like <see cref="HasFlag(uint,int)"/>, this method doesn't validate <paramref name="n"/>
|
||||
|
@ -60,7 +123,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
|
|||
/// Sets a bit to a specified value.
|
||||
/// </summary>
|
||||
/// <param name="value">The input <see cref="uint"/> value.</param>
|
||||
/// <param name="n">The position of the bit to set or clear.</param>
|
||||
/// <param name="n">The position of the bit to set or clear (in [0, 31] range).</param>
|
||||
/// <param name="flag">The value to assign to the target bit.</param>
|
||||
/// <returns>An <see cref="uint"/> value equal to <paramref name="value"/> except for the <paramref name="n"/>-th bit.</returns>
|
||||
/// <remarks>
|
||||
|
@ -71,41 +134,108 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static uint SetFlag(uint value, int n, bool flag)
|
||||
{
|
||||
/* Shift a bit left to the n-th position, negate the
|
||||
* resulting value and perform an AND with the input value.
|
||||
* This effectively clears the n-th bit of our input. */
|
||||
// Shift a bit left to the n-th position, negate the
|
||||
// resulting value and perform an AND with the input value.
|
||||
// This effectively clears the n-th bit of our input.
|
||||
uint
|
||||
bit = 1u << n,
|
||||
not = ~bit,
|
||||
and = value & not;
|
||||
|
||||
/* Reinterpret the flag as 1 or 0, and cast to uint.
|
||||
* The flag is first copied to a local variable as taking
|
||||
* the address of an argument is slower than doing the same
|
||||
* for a local variable. This is because when referencing
|
||||
* the argument the JIT compiler will emit code to temporarily
|
||||
* move the argument to the stack, and then copy it back.
|
||||
* With a temporary variable instead, the JIT will be able to
|
||||
* optimize the method to only use CPU registers. */
|
||||
bool localFlag = flag;
|
||||
// Reinterpret the flag as 1 or 0, and cast to uint,
|
||||
// then we left shift the uint flag to the right position
|
||||
// and perform an OR with the resulting value of the previous
|
||||
// operation. This will always guaranteed to work, thanks to the
|
||||
// initial code clearing that bit before setting it again.
|
||||
uint
|
||||
flag32 = Unsafe.As<bool, byte>(ref localFlag),
|
||||
|
||||
/* Finally, we left shift the uint flag to the right position
|
||||
* and perform an OR with the resulting value of the previous
|
||||
* operation. This will always guaranteed to work, thanks to the
|
||||
* initial code clearing that bit before setting it again. */
|
||||
flag32 = Unsafe.As<bool, byte>(ref flag),
|
||||
shift = flag32 << n,
|
||||
or = and | shift;
|
||||
|
||||
return or;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts a bit field range from a given value.
|
||||
/// </summary>
|
||||
/// <param name="value">The input <see cref="uint"/> value.</param>
|
||||
/// <param name="start">The initial index of the range to extract (in [0, 31] range).</param>
|
||||
/// <param name="length">The length of the range to extract (depends on <paramref name="start"/>).</param>
|
||||
/// <returns>The value of the extracted range within <paramref name="value"/>.</returns>
|
||||
/// <remarks>
|
||||
/// This method doesn't validate <paramref name="start"/> and <paramref name="length"/>.
|
||||
/// If either parameter is not valid, the result will just be inconsistent. The method
|
||||
/// should not be used to set all the bits at once, and it is not guaranteed to work in
|
||||
/// that case, which would just be equivalent to assigning the <see cref="uint"/> value.
|
||||
/// Additionally, no conditional branches are used to retrieve the range.
|
||||
/// </remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static uint ExtractRange(uint value, byte start, byte length)
|
||||
{
|
||||
#if NETCOREAPP3_1
|
||||
if (Bmi1.IsSupported)
|
||||
{
|
||||
return Bmi1.BitFieldExtract(value, start, length);
|
||||
}
|
||||
#endif
|
||||
|
||||
return (value >> start) & ((1u << length) - 1u);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a bit field range within a target value.
|
||||
/// </summary>
|
||||
/// <param name="value">The target <see cref="uint"/> value.</param>
|
||||
/// <param name="start">The initial index of the range to extract (in [0, 31] range).</param>
|
||||
/// <param name="length">The length of the range to extract (depends on <paramref name="start"/>).</param>
|
||||
/// <param name="flags">The input flags to insert in the target range.</param>
|
||||
/// <remarks>
|
||||
/// Just like <see cref="ExtractRange(uint,byte,byte)"/>, this method doesn't validate the parameters
|
||||
/// and does not contain branching instructions, so it's well suited for use in tight loops as well.
|
||||
/// </remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void SetRange(ref uint value, byte start, byte length, uint flags)
|
||||
{
|
||||
value = SetRange(value, start, length, flags);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a bit field range within a target value.
|
||||
/// </summary>
|
||||
/// <param name="value">The initial <see cref="uint"/> value.</param>
|
||||
/// <param name="start">The initial index of the range to extract (in [0, 31] range).</param>
|
||||
/// <param name="length">The length of the range to extract (depends on <paramref name="start"/>).</param>
|
||||
/// <param name="flags">The input flags to insert in the target range.</param>
|
||||
/// <returns>The updated bit field value after setting the specified range.</returns>
|
||||
/// <remarks>
|
||||
/// Just like <see cref="ExtractRange(uint,byte,byte)"/>, this method doesn't validate the parameters
|
||||
/// and does not contain branching instructions, so it's well suited for use in tight loops as well.
|
||||
/// </remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static uint SetRange(uint value, byte start, byte length, uint flags)
|
||||
{
|
||||
uint
|
||||
highBits = (1u << length) - 1u,
|
||||
loadMask = highBits << start,
|
||||
storeMask = (flags & highBits) << start;
|
||||
|
||||
#if NETCOREAPP3_1
|
||||
if (Bmi1.IsSupported)
|
||||
{
|
||||
return Bmi1.AndNot(loadMask, value) | storeMask;
|
||||
}
|
||||
#endif
|
||||
|
||||
return (~loadMask & value) | storeMask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether or not a given bit is set.
|
||||
/// </summary>
|
||||
/// <param name="value">The input <see cref="ulong"/> value.</param>
|
||||
/// <param name="n">The position of the bit to check.</param>
|
||||
/// <param name="n">The position of the bit to check (in [0, 63] range).</param>
|
||||
/// <returns>Whether or not the n-th bit is set.</returns>
|
||||
/// <remarks>
|
||||
/// This method doesn't validate <paramref name="n"/> against the valid range.
|
||||
|
@ -122,11 +252,41 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
|
|||
return Unsafe.As<byte, bool>(ref flag);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether or not a given bit is set in a given bitwise lookup table.
|
||||
/// For more info, check the XML docs of the <see cref="HasLookupFlag(uint,int,int)"/> overload.
|
||||
/// </summary>
|
||||
/// <param name="table">The input lookup table to use.</param>
|
||||
/// <param name="x">The input value to check.</param>
|
||||
/// <param name="min">The minimum accepted value for <paramref name="x"/> (defaults to 0).</param>
|
||||
/// <returns>Whether or not the corresponding flag for <paramref name="x"/> is set in <paramref name="table"/>.</returns>
|
||||
/// <remarks>
|
||||
/// For best results, as shown in the sample code, both <paramref name="table"/> and <paramref name="min"/>
|
||||
/// should be compile-time constants, so that the JIT compiler will be able to produce more efficient code.
|
||||
/// </remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool HasLookupFlag(ulong table, int x, int min = 0)
|
||||
{
|
||||
int i = x - min;
|
||||
bool isInRange = (uint)i < 64u;
|
||||
byte byteFlag = Unsafe.As<bool, byte>(ref isInRange);
|
||||
int
|
||||
negativeFlag = byteFlag - 1,
|
||||
mask = ~negativeFlag,
|
||||
shift = unchecked((int)((table >> i) & 1)),
|
||||
and = shift & mask;
|
||||
byte result = unchecked((byte)and);
|
||||
bool valid = Unsafe.As<byte, bool>(ref result);
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a bit to a specified value.
|
||||
/// </summary>
|
||||
/// <param name="value">The target <see cref="ulong"/> value.</param>
|
||||
/// <param name="n">The position of the bit to set or clear.</param>
|
||||
/// <param name="n">The position of the bit to set or clear (in [0, 63] range).</param>
|
||||
/// <param name="flag">The value to assign to the target bit.</param>
|
||||
/// <remarks>
|
||||
/// Just like <see cref="HasFlag(ulong,int)"/>, this method doesn't validate <paramref name="n"/>
|
||||
|
@ -142,7 +302,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
|
|||
/// Sets a bit to a specified value.
|
||||
/// </summary>
|
||||
/// <param name="value">The input <see cref="ulong"/> value.</param>
|
||||
/// <param name="n">The position of the bit to set or clear.</param>
|
||||
/// <param name="n">The position of the bit to set or clear (in [0, 63] range).</param>
|
||||
/// <param name="flag">The value to assign to the target bit.</param>
|
||||
/// <returns>An <see cref="ulong"/> value equal to <paramref name="value"/> except for the <paramref name="n"/>-th bit.</returns>
|
||||
/// <remarks>
|
||||
|
@ -153,18 +313,91 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ulong SetFlag(ulong value, int n, bool flag)
|
||||
{
|
||||
// As with the method above, reuse the same logic as the uint version
|
||||
ulong
|
||||
bit = 1ul << n,
|
||||
not = ~bit,
|
||||
and = value & not;
|
||||
bool localFlag = flag;
|
||||
ulong
|
||||
flag64 = Unsafe.As<bool, byte>(ref localFlag),
|
||||
and = value & not,
|
||||
flag64 = Unsafe.As<bool, byte>(ref flag),
|
||||
shift = flag64 << n,
|
||||
or = and | shift;
|
||||
|
||||
return or;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts a bit field range from a given value.
|
||||
/// </summary>
|
||||
/// <param name="value">The input <see cref="ulong"/> value.</param>
|
||||
/// <param name="start">The initial index of the range to extract (in [0, 63] range).</param>
|
||||
/// <param name="length">The length of the range to extract (depends on <paramref name="start"/>).</param>
|
||||
/// <returns>The value of the extracted range within <paramref name="value"/>.</returns>
|
||||
/// <remarks>
|
||||
/// This method doesn't validate <paramref name="start"/> and <paramref name="length"/>.
|
||||
/// If either parameter is not valid, the result will just be inconsistent. The method
|
||||
/// should not be used to set all the bits at once, and it is not guaranteed to work in
|
||||
/// that case, which would just be equivalent to assigning the <see cref="ulong"/> value.
|
||||
/// Additionally, no conditional branches are used to retrieve the range.
|
||||
/// </remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ulong ExtractRange(ulong value, byte start, byte length)
|
||||
{
|
||||
#if NETCOREAPP3_1
|
||||
if (Bmi1.X64.IsSupported)
|
||||
{
|
||||
return Bmi1.X64.BitFieldExtract(value, start, length);
|
||||
}
|
||||
#endif
|
||||
|
||||
return (value >> start) & ((1ul << length) - 1ul);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a bit field range within a target value.
|
||||
/// </summary>
|
||||
/// <param name="value">The target <see cref="ulong"/> value.</param>
|
||||
/// <param name="start">The initial index of the range to extract (in [0, 63] range).</param>
|
||||
/// <param name="length">The length of the range to extract (depends on <paramref name="start"/>).</param>
|
||||
/// <param name="flags">The input flags to insert in the target range.</param>
|
||||
/// <remarks>
|
||||
/// Just like <see cref="ExtractRange(ulong,byte,byte)"/>, this method doesn't validate the parameters
|
||||
/// and does not contain branching instructions, so it's well suited for use in tight loops as well.
|
||||
/// </remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void SetRange(ref ulong value, byte start, byte length, ulong flags)
|
||||
{
|
||||
value = SetRange(value, start, length, flags);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a bit field range within a target value.
|
||||
/// </summary>
|
||||
/// <param name="value">The initial <see cref="ulong"/> value.</param>
|
||||
/// <param name="start">The initial index of the range to extract (in [0, 63] range).</param>
|
||||
/// <param name="length">The length of the range to extract (depends on <paramref name="start"/>).</param>
|
||||
/// <param name="flags">The input flags to insert in the target range.</param>
|
||||
/// <returns>The updated bit field value after setting the specified range.</returns>
|
||||
/// <remarks>
|
||||
/// Just like <see cref="ExtractRange(ulong,byte,byte)"/>, this method doesn't validate the parameters
|
||||
/// and does not contain branching instructions, so it's well suited for use in tight loops as well.
|
||||
/// </remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ulong SetRange(ulong value, byte start, byte length, ulong flags)
|
||||
{
|
||||
ulong
|
||||
highBits = (1ul << length) - 1ul,
|
||||
loadMask = highBits << start,
|
||||
storeMask = (flags & highBits) << start;
|
||||
|
||||
#if NETCOREAPP3_1
|
||||
if (Bmi1.X64.IsSupported)
|
||||
{
|
||||
return Bmi1.X64.AndNot(loadMask, value) | storeMask;
|
||||
}
|
||||
#endif
|
||||
|
||||
return (~loadMask & value) | storeMask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,12 +2,13 @@
|
|||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
#if !NETSTANDARD1_4
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Toolkit.HighPerformance.Helpers.Internals;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Helpers
|
||||
{
|
||||
|
@ -15,13 +16,21 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
|
|||
/// Combines the hash code of sequences of <typeparamref name="T"/> values into a single hash code.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of values to hash.</typeparam>
|
||||
/// <remarks>
|
||||
/// The hash codes returned by the <see cref="Combine"/> method are only guaranteed to be repeatable for
|
||||
/// the current execution session, just like with the available <see cref="HashCode"/> APIs.In other words,
|
||||
/// hashing the same <see cref="ReadOnlySpan{T}"/> collection multiple times in the same process will always
|
||||
/// result in the same hash code, while the same collection being hashed again from another process
|
||||
/// (or another instance of the same process) is not guaranteed to result in the same final value.
|
||||
/// For more info, see <see href="https://docs.microsoft.com/en-us/dotnet/api/system.object.gethashcode#remarks"/>.
|
||||
/// </remarks>
|
||||
public struct HashCode<T>
|
||||
#if NETSTANDARD2_1
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
where T : notnull
|
||||
#else
|
||||
/* .NET Standard 2.0 doesn't have the API to check at runtime whether a
|
||||
* type satisfies the unmanaged constraint, se we enforce that at compile
|
||||
* time and only expose the APIs of this class in that case. */
|
||||
// If we lack the RuntimeHelpers.IsReferenceOrContainsReferences<T> API,
|
||||
// we need to constraint the generic type parameter to unmanaged, as we
|
||||
// wouldn't otherwise be able to properly validate it at runtime.
|
||||
where T : unmanaged
|
||||
#endif
|
||||
{
|
||||
|
@ -48,365 +57,27 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
|
|||
/// <remarks>The returned hash code is not processed through <see cref="HashCode"/> APIs.</remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1515", Justification = "Compiler directive instead of whitespace")]
|
||||
internal static int CombineValues(ReadOnlySpan<T> span)
|
||||
{
|
||||
ref T r0 = ref MemoryMarshal.GetReference(span);
|
||||
|
||||
#if NETSTANDARD2_1
|
||||
/* If typeof(T) is not unmanaged, iterate over all the items one by one.
|
||||
* This check is always known in advance either by the JITter or by the AOT
|
||||
* compiler, so this branch will never actually be executed by the code. */
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
// If typeof(T) is not unmanaged, iterate over all the items one by one.
|
||||
// This check is always known in advance either by the JITter or by the AOT
|
||||
// compiler, so this branch will never actually be executed by the code.
|
||||
if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
|
||||
{
|
||||
return CombineValues(ref r0, span.Length);
|
||||
return SpanHelper.GetDjb2HashCode(ref r0, (IntPtr)span.Length);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Get the info for the target memory area to process
|
||||
ref byte rb = ref Unsafe.As<T, byte>(ref r0);
|
||||
long byteSize = (long)span.Length * Unsafe.SizeOf<T>();
|
||||
IntPtr length = (IntPtr)((long)span.Length * Unsafe.SizeOf<T>());
|
||||
|
||||
// Fast path if the source memory area is not large enough
|
||||
if (byteSize < BytesProcessor.MinimumSuggestedSize)
|
||||
{
|
||||
return CombineValues(ref r0, span.Length);
|
||||
}
|
||||
|
||||
// Use the fast vectorized overload if the input span can be reinterpreted as a sequence of bytes
|
||||
return byteSize <= int.MaxValue
|
||||
? BytesProcessor.CombineBytes(ref rb, unchecked((int)byteSize))
|
||||
: BytesProcessor.CombineBytes(ref rb, byteSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a content hash from the input memory area.
|
||||
/// </summary>
|
||||
/// <param name="r0">A <typeparamref name="T"/> reference to the start of the memory area.</param>
|
||||
/// <param name="length">The size of the memory area.</param>
|
||||
/// <returns>The hash code for the input values.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static int CombineValues(ref T r0, int length)
|
||||
{
|
||||
int
|
||||
hash = 0, i = 0,
|
||||
end8 = length - 8;
|
||||
|
||||
// Main loop with 8 unrolled iterations
|
||||
for (; i <= end8; i += 8)
|
||||
{
|
||||
hash = unchecked((hash * 397) ^ Unsafe.Add(ref r0, i + 0).GetHashCode());
|
||||
hash = unchecked((hash * 397) ^ Unsafe.Add(ref r0, i + 1).GetHashCode());
|
||||
hash = unchecked((hash * 397) ^ Unsafe.Add(ref r0, i + 2).GetHashCode());
|
||||
hash = unchecked((hash * 397) ^ Unsafe.Add(ref r0, i + 3).GetHashCode());
|
||||
hash = unchecked((hash * 397) ^ Unsafe.Add(ref r0, i + 4).GetHashCode());
|
||||
hash = unchecked((hash * 397) ^ Unsafe.Add(ref r0, i + 5).GetHashCode());
|
||||
hash = unchecked((hash * 397) ^ Unsafe.Add(ref r0, i + 6).GetHashCode());
|
||||
hash = unchecked((hash * 397) ^ Unsafe.Add(ref r0, i + 7).GetHashCode());
|
||||
}
|
||||
|
||||
// Handle the leftover items
|
||||
for (; i < length; i++)
|
||||
{
|
||||
hash = unchecked((hash * 397) ^ Unsafe.Add(ref r0, i).GetHashCode());
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Combines the hash code of sequences of <see cref="byte"/> values into a single hash code.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This type is just a wrapper for the <see cref="CombineBytes(ref byte, int)"/> method,
|
||||
/// which is not defined within <see cref="HashCode{T}"/> for performance reasons.
|
||||
/// Because <see cref="HashCode{T}"/> is a generic type, each contained method will be JIT
|
||||
/// compiled into a different executable, even if it's not directly using the generic type
|
||||
/// parameters of the declaring type at all. Moving this method into a separate, non-generic
|
||||
/// type allows the runtime to always reuse a single JIT compilation.
|
||||
/// </remarks>
|
||||
internal static class BytesProcessor
|
||||
{
|
||||
/// <summary>
|
||||
/// The minimum suggested size for memory areas to process using the APIs in this class
|
||||
/// </summary>
|
||||
public const int MinimumSuggestedSize = 512;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a content hash from a given memory area.
|
||||
/// </summary>
|
||||
/// <param name="r0">A <see cref="byte"/> reference to the start of the memory area.</param>
|
||||
/// <param name="length">The size in bytes of the memory area.</param>
|
||||
/// <returns>The hash code for the contents of the source memory area.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static int CombineBytes(ref byte r0, long length)
|
||||
{
|
||||
/* This method takes a long as the length parameter, which is needed in case
|
||||
* the target area is a large sequence of items with a byte size greater than
|
||||
* one. To maximize efficiency, the target memory area is divided into
|
||||
* contiguous blocks as large as possible, and the SIMD implementation
|
||||
* is executed on all of them one by one. The partial results
|
||||
* are accumulated with the usual hash function. */
|
||||
int
|
||||
hash = 0,
|
||||
runs = unchecked((int)(length / int.MaxValue)),
|
||||
trailing = unchecked((int)(length - (runs * (long)int.MaxValue)));
|
||||
|
||||
// Process chunks of int.MaxValue consecutive bytes
|
||||
for (int i = 0; i < runs; i++)
|
||||
{
|
||||
int partial = CombineBytes(ref r0, int.MaxValue);
|
||||
|
||||
hash = unchecked((hash * 397) ^ partial);
|
||||
|
||||
r0 = ref Unsafe.Add(ref r0, int.MaxValue);
|
||||
}
|
||||
|
||||
// Process the leftover elements
|
||||
int tail = CombineBytes(ref r0, trailing);
|
||||
|
||||
hash = unchecked((hash * 397) ^ tail);
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a content hash from a given memory area.
|
||||
/// </summary>
|
||||
/// <param name="r0">A <see cref="byte"/> reference to the start of the memory area.</param>
|
||||
/// <param name="length">The size in bytes of the memory area.</param>
|
||||
/// <returns>The hash code for the contents of the source memory area.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static int CombineBytes(ref byte r0, int length)
|
||||
{
|
||||
int hash = 0, i = 0;
|
||||
|
||||
// Dedicated SIMD branch, if available
|
||||
if (Vector.IsHardwareAccelerated)
|
||||
{
|
||||
/* Test whether the total number of bytes is at least
|
||||
* equal to the number that can fit in a single SIMD register.
|
||||
* If that is not the case, skip the entire SIMD branch, which
|
||||
* also saves the unnecessary computation of partial hash
|
||||
* values from the accumulation register, and the loading
|
||||
* of the prime constant in the secondary SIMD register. */
|
||||
if (length >= 8 * Vector<int>.Count * sizeof(int))
|
||||
{
|
||||
var vh = Vector<int>.Zero;
|
||||
var v397 = new Vector<int>(397);
|
||||
|
||||
/* First upper bound for the vectorized path.
|
||||
* The first loop has sequences of 8 SIMD operations unrolled,
|
||||
* so assuming that a SIMD register can hold 8 int values at a time,
|
||||
* it processes 8 * Vector<int>.Count * sizeof(int), which results in
|
||||
* 128 bytes on SSE registers, 256 on AVX2 and 512 on AVX512 registers. */
|
||||
var end256 = length - (8 * Vector<int>.Count * sizeof(int));
|
||||
|
||||
for (; i <= end256; i += 8 * Vector<int>.Count * sizeof(int))
|
||||
{
|
||||
ref byte ri0 = ref Unsafe.Add(ref r0, (Vector<int>.Count * sizeof(int) * 0) + i);
|
||||
var vi0 = Unsafe.ReadUnaligned<Vector<int>>(ref ri0);
|
||||
var vp0 = Vector.Multiply(vh, v397);
|
||||
vh = Vector.Xor(vp0, vi0);
|
||||
|
||||
ref byte ri1 = ref Unsafe.Add(ref r0, (Vector<int>.Count * sizeof(int) * 1) + i);
|
||||
var vi1 = Unsafe.ReadUnaligned<Vector<int>>(ref ri1);
|
||||
var vp1 = Vector.Multiply(vh, v397);
|
||||
vh = Vector.Xor(vp1, vi1);
|
||||
|
||||
ref byte ri2 = ref Unsafe.Add(ref r0, (Vector<int>.Count * sizeof(int) * 2) + i);
|
||||
var vi2 = Unsafe.ReadUnaligned<Vector<int>>(ref ri2);
|
||||
var vp2 = Vector.Multiply(vh, v397);
|
||||
vh = Vector.Xor(vp2, vi2);
|
||||
|
||||
ref byte ri3 = ref Unsafe.Add(ref r0, (Vector<int>.Count * sizeof(int) * 3) + i);
|
||||
var vi3 = Unsafe.ReadUnaligned<Vector<int>>(ref ri3);
|
||||
var vp3 = Vector.Multiply(vh, v397);
|
||||
vh = Vector.Xor(vp3, vi3);
|
||||
|
||||
ref byte ri4 = ref Unsafe.Add(ref r0, (Vector<int>.Count * sizeof(int) * 4) + i);
|
||||
var vi4 = Unsafe.ReadUnaligned<Vector<int>>(ref ri4);
|
||||
var vp4 = Vector.Multiply(vh, v397);
|
||||
vh = Vector.Xor(vp4, vi4);
|
||||
|
||||
ref byte ri5 = ref Unsafe.Add(ref r0, (Vector<int>.Count * sizeof(int) * 5) + i);
|
||||
var vi5 = Unsafe.ReadUnaligned<Vector<int>>(ref ri5);
|
||||
var vp5 = Vector.Multiply(vh, v397);
|
||||
vh = Vector.Xor(vp5, vi5);
|
||||
|
||||
ref byte ri6 = ref Unsafe.Add(ref r0, (Vector<int>.Count * sizeof(int) * 6) + i);
|
||||
var vi6 = Unsafe.ReadUnaligned<Vector<int>>(ref ri6);
|
||||
var vp6 = Vector.Multiply(vh, v397);
|
||||
vh = Vector.Xor(vp6, vi6);
|
||||
|
||||
ref byte ri7 = ref Unsafe.Add(ref r0, (Vector<int>.Count * sizeof(int) * 7) + i);
|
||||
var vi7 = Unsafe.ReadUnaligned<Vector<int>>(ref ri7);
|
||||
var vp7 = Vector.Multiply(vh, v397);
|
||||
vh = Vector.Xor(vp7, vi7);
|
||||
}
|
||||
|
||||
/* Second upper bound for the vectorized path.
|
||||
* Each iteration processes 16 bytes on SSE, 32 bytes on AVX2
|
||||
* and 64 on AVX512 registers. When this point is reached,
|
||||
* it means that there are at most 127 bytes remaining on SSE,
|
||||
* or 255 on AVX2, or 511 on AVX512 systems.*/
|
||||
var end32 = length - i - (Vector<int>.Count * sizeof(int));
|
||||
|
||||
for (; i <= end32; i += Vector<int>.Count * sizeof(int))
|
||||
{
|
||||
ref byte ri = ref Unsafe.Add(ref r0, i);
|
||||
var vi = Unsafe.ReadUnaligned<Vector<int>>(ref ri);
|
||||
var vp = Vector.Multiply(vh, v397);
|
||||
vh = Vector.Xor(vp, vi);
|
||||
}
|
||||
|
||||
/* Combine the partial hash values in each position.
|
||||
* The loop below is automatically unrolled by the JIT. */
|
||||
for (var j = 0; j < Vector<int>.Count; j++)
|
||||
{
|
||||
hash = unchecked((hash * 397) ^ vh[j]);
|
||||
}
|
||||
}
|
||||
|
||||
/* At this point, regardless of whether or not the previous
|
||||
* branch was taken, there are at most 15 unprocessed bytes
|
||||
* on SSE systems, 31 on AVX2 systems and 63 on AVX512 systems. */
|
||||
}
|
||||
else
|
||||
{
|
||||
// Process groups of 64 bytes at a time
|
||||
var end64 = length - (8 * sizeof(ulong));
|
||||
|
||||
for (; i <= end64; i += 8 * sizeof(ulong))
|
||||
{
|
||||
ref byte ri0 = ref Unsafe.Add(ref r0, (sizeof(ulong) * 0) + i);
|
||||
var value0 = Unsafe.ReadUnaligned<ulong>(ref ri0);
|
||||
hash = unchecked((hash * 397) ^ (int)value0 ^ (int)(value0 >> 32));
|
||||
|
||||
ref byte ri1 = ref Unsafe.Add(ref r0, (sizeof(ulong) * 1) + i);
|
||||
var value1 = Unsafe.ReadUnaligned<ulong>(ref ri1);
|
||||
hash = unchecked((hash * 397) ^ (int)value1 ^ (int)(value1 >> 32));
|
||||
|
||||
ref byte ri2 = ref Unsafe.Add(ref r0, (sizeof(ulong) * 2) + i);
|
||||
var value2 = Unsafe.ReadUnaligned<ulong>(ref ri2);
|
||||
hash = unchecked((hash * 397) ^ (int)value2 ^ (int)(value2 >> 32));
|
||||
|
||||
ref byte ri3 = ref Unsafe.Add(ref r0, (sizeof(ulong) * 3) + i);
|
||||
var value3 = Unsafe.ReadUnaligned<ulong>(ref ri3);
|
||||
hash = unchecked((hash * 397) ^ (int)value3 ^ (int)(value3 >> 32));
|
||||
|
||||
ref byte ri4 = ref Unsafe.Add(ref r0, (sizeof(ulong) * 4) + i);
|
||||
var value4 = Unsafe.ReadUnaligned<ulong>(ref ri4);
|
||||
hash = unchecked((hash * 397) ^ (int)value4 ^ (int)(value4 >> 32));
|
||||
|
||||
ref byte ri5 = ref Unsafe.Add(ref r0, (sizeof(ulong) * 5) + i);
|
||||
var value5 = Unsafe.ReadUnaligned<ulong>(ref ri5);
|
||||
hash = unchecked((hash * 397) ^ (int)value5 ^ (int)(value5 >> 32));
|
||||
|
||||
ref byte ri6 = ref Unsafe.Add(ref r0, (sizeof(ulong) * 6) + i);
|
||||
var value6 = Unsafe.ReadUnaligned<ulong>(ref ri6);
|
||||
hash = unchecked((hash * 397) ^ (int)value6 ^ (int)(value6 >> 32));
|
||||
|
||||
ref byte ri7 = ref Unsafe.Add(ref r0, (sizeof(ulong) * 7) + i);
|
||||
var value7 = Unsafe.ReadUnaligned<ulong>(ref ri7);
|
||||
hash = unchecked((hash * 397) ^ (int)value7 ^ (int)(value7 >> 32));
|
||||
}
|
||||
|
||||
/* At this point, there are up to 63 bytes left.
|
||||
* If there are at least 32, unroll that iteration with
|
||||
* the same procedure as before, but as uint values. */
|
||||
if (length - i >= 8 * sizeof(uint))
|
||||
{
|
||||
ref byte ri0 = ref Unsafe.Add(ref r0, (sizeof(uint) * 0) + i);
|
||||
var value0 = Unsafe.ReadUnaligned<uint>(ref ri0);
|
||||
hash = unchecked((hash * 397) ^ (int)value0);
|
||||
|
||||
ref byte ri1 = ref Unsafe.Add(ref r0, (sizeof(uint) * 1) + i);
|
||||
var value1 = Unsafe.ReadUnaligned<uint>(ref ri1);
|
||||
hash = unchecked((hash * 397) ^ (int)value1);
|
||||
|
||||
ref byte ri2 = ref Unsafe.Add(ref r0, (sizeof(uint) * 2) + i);
|
||||
var value2 = Unsafe.ReadUnaligned<uint>(ref ri2);
|
||||
hash = unchecked((hash * 397) ^ (int)value2);
|
||||
|
||||
ref byte ri3 = ref Unsafe.Add(ref r0, (sizeof(uint) * 3) + i);
|
||||
var value3 = Unsafe.ReadUnaligned<uint>(ref ri3);
|
||||
hash = unchecked((hash * 397) ^ (int)value3);
|
||||
|
||||
ref byte ri4 = ref Unsafe.Add(ref r0, (sizeof(uint) * 4) + i);
|
||||
var value4 = Unsafe.ReadUnaligned<uint>(ref ri4);
|
||||
hash = unchecked((hash * 397) ^ (int)value4);
|
||||
|
||||
ref byte ri5 = ref Unsafe.Add(ref r0, (sizeof(uint) * 5) + i);
|
||||
var value5 = Unsafe.ReadUnaligned<uint>(ref ri5);
|
||||
hash = unchecked((hash * 397) ^ (int)value5);
|
||||
|
||||
ref byte ri6 = ref Unsafe.Add(ref r0, (sizeof(uint) * 6) + i);
|
||||
var value6 = Unsafe.ReadUnaligned<uint>(ref ri6);
|
||||
hash = unchecked((hash * 397) ^ (int)value6);
|
||||
|
||||
ref byte ri7 = ref Unsafe.Add(ref r0, (sizeof(uint) * 7) + i);
|
||||
var value7 = Unsafe.ReadUnaligned<uint>(ref ri7);
|
||||
hash = unchecked((hash * 397) ^ (int)value7);
|
||||
}
|
||||
|
||||
// The non-SIMD path leaves up to 31 unprocessed bytes
|
||||
}
|
||||
|
||||
/* At this point there might be up to 31 bytes left on both AVX2 systems,
|
||||
* and on systems with no hardware accelerated SIMD registers.
|
||||
* That number would go up to 63 on AVX512 systems, in which case it is
|
||||
* still useful to perform this last loop unrolling.
|
||||
* The only case where this branch is never taken is on SSE systems,
|
||||
* but since those are not so common anyway the code is left here for simplicity.
|
||||
* What follows is the same procedure as before, but with ushort values,
|
||||
* so that if there are at least 16 bytes available, those
|
||||
* will all be processed in a single unrolled iteration. */
|
||||
if (length - i >= 8 * sizeof(ushort))
|
||||
{
|
||||
ref byte ri0 = ref Unsafe.Add(ref r0, (sizeof(ushort) * 0) + i);
|
||||
var value0 = Unsafe.ReadUnaligned<ushort>(ref ri0);
|
||||
hash = unchecked((hash * 397) ^ value0);
|
||||
|
||||
ref byte ri1 = ref Unsafe.Add(ref r0, (sizeof(ushort) * 1) + i);
|
||||
var value1 = Unsafe.ReadUnaligned<ushort>(ref ri1);
|
||||
hash = unchecked((hash * 397) ^ value1);
|
||||
|
||||
ref byte ri2 = ref Unsafe.Add(ref r0, (sizeof(ushort) * 2) + i);
|
||||
var value2 = Unsafe.ReadUnaligned<ushort>(ref ri2);
|
||||
hash = unchecked((hash * 397) ^ value2);
|
||||
|
||||
ref byte ri3 = ref Unsafe.Add(ref r0, (sizeof(ushort) * 3) + i);
|
||||
var value3 = Unsafe.ReadUnaligned<ushort>(ref ri3);
|
||||
hash = unchecked((hash * 397) ^ value3);
|
||||
|
||||
ref byte ri4 = ref Unsafe.Add(ref r0, (sizeof(ushort) * 4) + i);
|
||||
var value4 = Unsafe.ReadUnaligned<ushort>(ref ri4);
|
||||
hash = unchecked((hash * 397) ^ value4);
|
||||
|
||||
ref byte ri5 = ref Unsafe.Add(ref r0, (sizeof(ushort) * 5) + i);
|
||||
var value5 = Unsafe.ReadUnaligned<ushort>(ref ri5);
|
||||
hash = unchecked((hash * 397) ^ value5);
|
||||
|
||||
ref byte ri6 = ref Unsafe.Add(ref r0, (sizeof(ushort) * 6) + i);
|
||||
var value6 = Unsafe.ReadUnaligned<ushort>(ref ri6);
|
||||
hash = unchecked((hash * 397) ^ value6);
|
||||
|
||||
ref byte ri7 = ref Unsafe.Add(ref r0, (sizeof(ushort) * 7) + i);
|
||||
var value7 = Unsafe.ReadUnaligned<ushort>(ref ri7);
|
||||
hash = unchecked((hash * 397) ^ value7);
|
||||
}
|
||||
|
||||
// Handle the leftover items
|
||||
for (; i < length; i++)
|
||||
{
|
||||
hash = unchecked((hash * 397) ^ Unsafe.Add(ref r0, i));
|
||||
}
|
||||
|
||||
return hash;
|
||||
return SpanHelper.GetDjb2LikeByteHash(ref rb, length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -0,0 +1,305 @@
|
|||
// 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.Diagnostics.Contracts;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.Toolkit.HighPerformance.Extensions;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers to process sequences of values by reference.
|
||||
/// </summary>
|
||||
internal static partial class SpanHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Counts the number of occurrences of a given value into a target search space.
|
||||
/// </summary>
|
||||
/// <param name="r0">A <typeparamref name="T"/> reference to the start of the search space.</param>
|
||||
/// <param name="length">The number of items in the search space.</param>
|
||||
/// <param name="value">The <typeparamref name="T"/> value to look for.</param>
|
||||
/// <typeparam name="T">The type of value to look for.</typeparam>
|
||||
/// <returns>The number of occurrences of <paramref name="value"/> in the search space</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int Count<T>(ref T r0, IntPtr length, T value)
|
||||
where T : IEquatable<T>
|
||||
{
|
||||
if (!Vector.IsHardwareAccelerated)
|
||||
{
|
||||
return CountSequential(ref r0, length, value);
|
||||
}
|
||||
|
||||
// Special vectorized version when using a supported type
|
||||
if (typeof(T) == typeof(byte) ||
|
||||
typeof(T) == typeof(sbyte) ||
|
||||
typeof(T) == typeof(bool))
|
||||
{
|
||||
ref sbyte r1 = ref Unsafe.As<T, sbyte>(ref r0);
|
||||
sbyte target = Unsafe.As<T, sbyte>(ref value);
|
||||
|
||||
return CountSimd(ref r1, length, target, (IntPtr)sbyte.MaxValue);
|
||||
}
|
||||
|
||||
if (typeof(T) == typeof(char) ||
|
||||
typeof(T) == typeof(ushort) ||
|
||||
typeof(T) == typeof(short))
|
||||
{
|
||||
ref short r1 = ref Unsafe.As<T, short>(ref r0);
|
||||
short target = Unsafe.As<T, short>(ref value);
|
||||
|
||||
return CountSimd(ref r1, length, target, (IntPtr)short.MaxValue);
|
||||
}
|
||||
|
||||
if (typeof(T) == typeof(int) ||
|
||||
typeof(T) == typeof(uint))
|
||||
{
|
||||
ref int r1 = ref Unsafe.As<T, int>(ref r0);
|
||||
int target = Unsafe.As<T, int>(ref value);
|
||||
|
||||
return CountSimd(ref r1, length, target, (IntPtr)int.MaxValue);
|
||||
}
|
||||
|
||||
if (typeof(T) == typeof(long) ||
|
||||
typeof(T) == typeof(ulong))
|
||||
{
|
||||
ref long r1 = ref Unsafe.As<T, long>(ref r0);
|
||||
long target = Unsafe.As<T, long>(ref value);
|
||||
|
||||
return CountSimd(ref r1, length, target, (IntPtr)int.MaxValue);
|
||||
}
|
||||
|
||||
return CountSequential(ref r0, length, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements <see cref="Count{T}"/> with a sequential search.
|
||||
/// </summary>
|
||||
[Pure]
|
||||
#if NETCOREAPP3_1
|
||||
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
||||
#endif
|
||||
private static unsafe int CountSequential<T>(ref T r0, IntPtr length, T value)
|
||||
where T : IEquatable<T>
|
||||
{
|
||||
int result = 0;
|
||||
|
||||
IntPtr offset = default;
|
||||
|
||||
// Main loop with 8 unrolled iterations
|
||||
while ((byte*)length >= (byte*)8)
|
||||
{
|
||||
result += Unsafe.Add(ref r0, offset + 0).Equals(value).ToInt();
|
||||
result += Unsafe.Add(ref r0, offset + 1).Equals(value).ToInt();
|
||||
result += Unsafe.Add(ref r0, offset + 2).Equals(value).ToInt();
|
||||
result += Unsafe.Add(ref r0, offset + 3).Equals(value).ToInt();
|
||||
result += Unsafe.Add(ref r0, offset + 4).Equals(value).ToInt();
|
||||
result += Unsafe.Add(ref r0, offset + 5).Equals(value).ToInt();
|
||||
result += Unsafe.Add(ref r0, offset + 6).Equals(value).ToInt();
|
||||
result += Unsafe.Add(ref r0, offset + 7).Equals(value).ToInt();
|
||||
|
||||
length -= 8;
|
||||
offset += 8;
|
||||
}
|
||||
|
||||
if ((byte*)length >= (byte*)4)
|
||||
{
|
||||
result += Unsafe.Add(ref r0, offset + 0).Equals(value).ToInt();
|
||||
result += Unsafe.Add(ref r0, offset + 1).Equals(value).ToInt();
|
||||
result += Unsafe.Add(ref r0, offset + 2).Equals(value).ToInt();
|
||||
result += Unsafe.Add(ref r0, offset + 3).Equals(value).ToInt();
|
||||
|
||||
length -= 4;
|
||||
offset += 4;
|
||||
}
|
||||
|
||||
// Iterate over the remaining values and count those that match
|
||||
while ((byte*)length > (byte*)0)
|
||||
{
|
||||
result += Unsafe.Add(ref r0, offset).Equals(value).ToInt();
|
||||
|
||||
length -= 1;
|
||||
offset += 1;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements <see cref="Count{T}"/> with a vectorized search.
|
||||
/// </summary>
|
||||
[Pure]
|
||||
#if NETCOREAPP3_1
|
||||
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
||||
#endif
|
||||
private static unsafe int CountSimd<T>(ref T r0, IntPtr length, T value, IntPtr max)
|
||||
where T : unmanaged, IEquatable<T>
|
||||
{
|
||||
int result = 0;
|
||||
|
||||
IntPtr offset = default;
|
||||
|
||||
// Skip the initialization overhead if there are not enough items
|
||||
if ((byte*)length >= (byte*)Vector<T>.Count)
|
||||
{
|
||||
var vc = new Vector<T>(value);
|
||||
|
||||
do
|
||||
{
|
||||
// Calculate the maximum sequential area that can be processed in
|
||||
// one pass without the risk of numeric overflow in the dot product
|
||||
// to sum the partial results. We also backup the current offset to
|
||||
// be able to track how many items have been processed, which lets
|
||||
// us avoid updating a third counter (length) in the loop body.
|
||||
IntPtr
|
||||
chunkLength = Min(length, max),
|
||||
initialOffset = offset;
|
||||
|
||||
var partials = Vector<T>.Zero;
|
||||
|
||||
while ((byte*)chunkLength >= (byte*)Vector<T>.Count)
|
||||
{
|
||||
ref T ri = ref Unsafe.Add(ref r0, offset);
|
||||
|
||||
// Load the current Vector<T> register, and then use
|
||||
// Vector.Equals to check for matches. This API sets the
|
||||
// values corresponding to matching pairs to all 1s.
|
||||
// Since the input type is guaranteed to always be signed,
|
||||
// this means that a value with all 1s represents -1, as
|
||||
// signed numbers are represented in two's complement.
|
||||
// So we can just subtract this intermediate value to the
|
||||
// partial results, which effectively sums 1 for each match.
|
||||
var vi = Unsafe.As<T, Vector<T>>(ref ri);
|
||||
var ve = Vector.Equals(vi, vc);
|
||||
|
||||
partials -= ve;
|
||||
|
||||
chunkLength -= Vector<T>.Count;
|
||||
offset += Vector<T>.Count;
|
||||
}
|
||||
|
||||
result += CastToInt(Vector.Dot(partials, Vector<T>.One));
|
||||
|
||||
length = Subtract(length, Subtract(offset, initialOffset));
|
||||
}
|
||||
while ((byte*)length >= (byte*)Vector<T>.Count);
|
||||
}
|
||||
|
||||
// Optional 8 unrolled iterations. This is only done when a single SIMD
|
||||
// register can contain over 8 values of the current type, as otherwise
|
||||
// there could never be enough items left after the vectorized path
|
||||
if (Vector<T>.Count > 8 &&
|
||||
(byte*)length >= (byte*)8)
|
||||
{
|
||||
result += Unsafe.Add(ref r0, offset + 0).Equals(value).ToInt();
|
||||
result += Unsafe.Add(ref r0, offset + 1).Equals(value).ToInt();
|
||||
result += Unsafe.Add(ref r0, offset + 2).Equals(value).ToInt();
|
||||
result += Unsafe.Add(ref r0, offset + 3).Equals(value).ToInt();
|
||||
result += Unsafe.Add(ref r0, offset + 4).Equals(value).ToInt();
|
||||
result += Unsafe.Add(ref r0, offset + 5).Equals(value).ToInt();
|
||||
result += Unsafe.Add(ref r0, offset + 6).Equals(value).ToInt();
|
||||
result += Unsafe.Add(ref r0, offset + 7).Equals(value).ToInt();
|
||||
|
||||
length -= 8;
|
||||
offset += 8;
|
||||
}
|
||||
|
||||
// Optional 4 unrolled iterations
|
||||
if (Vector<T>.Count > 4 &&
|
||||
(byte*)length >= (byte*)4)
|
||||
{
|
||||
result += Unsafe.Add(ref r0, offset + 0).Equals(value).ToInt();
|
||||
result += Unsafe.Add(ref r0, offset + 1).Equals(value).ToInt();
|
||||
result += Unsafe.Add(ref r0, offset + 2).Equals(value).ToInt();
|
||||
result += Unsafe.Add(ref r0, offset + 3).Equals(value).ToInt();
|
||||
|
||||
length -= 4;
|
||||
offset += 4;
|
||||
}
|
||||
|
||||
// Iterate over the remaining values and count those that match
|
||||
while ((byte*)length > (byte*)0)
|
||||
{
|
||||
result += Unsafe.Add(ref r0, offset).Equals(value).ToInt();
|
||||
|
||||
length -= 1;
|
||||
offset += 1;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the minimum between two <see cref="IntPtr"/> values.
|
||||
/// </summary>
|
||||
/// <param name="a">The first <see cref="IntPtr"/> value.</param>
|
||||
/// <param name="b">The second <see cref="IntPtr"/> value</param>
|
||||
/// <returns>The minimum between <paramref name="a"/> and <paramref name="b"/>.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static unsafe IntPtr Min(IntPtr a, IntPtr b)
|
||||
{
|
||||
if (sizeof(IntPtr) == 4)
|
||||
{
|
||||
return (IntPtr)Math.Min((int)a, (int)b);
|
||||
}
|
||||
|
||||
return (IntPtr)Math.Min((long)a, (long)b);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the difference between two <see cref="IntPtr"/> values.
|
||||
/// </summary>
|
||||
/// <param name="a">The first <see cref="IntPtr"/> value.</param>
|
||||
/// <param name="b">The second <see cref="IntPtr"/> value</param>
|
||||
/// <returns>The difference between <paramref name="a"/> and <paramref name="b"/>.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static unsafe IntPtr Subtract(IntPtr a, IntPtr b)
|
||||
{
|
||||
if (sizeof(IntPtr) == 4)
|
||||
{
|
||||
return (IntPtr)((int)a - (int)b);
|
||||
}
|
||||
|
||||
return (IntPtr)((long)a - (long)b);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Casts a value of a given type to <see cref="int"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The input type to cast.</typeparam>
|
||||
/// <param name="value">The input <typeparamref name="T"/> value to cast to <see cref="int"/>.</param>
|
||||
/// <returns>The <see cref="int"/> cast of <paramref name="value"/>.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int CastToInt<T>(T value)
|
||||
where T : unmanaged
|
||||
{
|
||||
if (typeof(T) == typeof(sbyte))
|
||||
{
|
||||
return Unsafe.As<T, sbyte>(ref value);
|
||||
}
|
||||
|
||||
if (typeof(T) == typeof(short))
|
||||
{
|
||||
return Unsafe.As<T, short>(ref value);
|
||||
}
|
||||
|
||||
if (typeof(T) == typeof(int))
|
||||
{
|
||||
return Unsafe.As<T, int>(ref value);
|
||||
}
|
||||
|
||||
if (typeof(T) == typeof(long))
|
||||
{
|
||||
return (int)Unsafe.As<T, long>(ref value);
|
||||
}
|
||||
|
||||
throw new NotSupportedException($"Invalid input type {typeof(T)}");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,324 @@
|
|||
// 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.Diagnostics.Contracts;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers to process sequences of values by reference.
|
||||
/// </summary>
|
||||
internal static partial class SpanHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Calculates the djb2 hash for the target sequence of items of a given type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items to hash.</typeparam>
|
||||
/// <param name="r0">The reference to the target memory area to hash.</param>
|
||||
/// <param name="length">The number of items to hash.</param>
|
||||
/// <returns>The Djb2 value for the input sequence of items.</returns>
|
||||
[Pure]
|
||||
#if NETCOREAPP3_1
|
||||
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
||||
#endif
|
||||
public static unsafe int GetDjb2HashCode<T>(ref T r0, IntPtr length)
|
||||
where T : notnull
|
||||
{
|
||||
int hash = 5381;
|
||||
|
||||
IntPtr offset = default;
|
||||
|
||||
while ((byte*)length >= (byte*)8)
|
||||
{
|
||||
// Doing a left shift by 5 and adding is equivalent to multiplying by 33.
|
||||
// This is preferred for performance reasons, as when working with integer
|
||||
// values most CPUs have higher latency for multiplication operations
|
||||
// compared to a simple shift and add. For more info on this, see the
|
||||
// details for imul, shl, add: https://gmplib.org/~tege/x86-timing.pdf.
|
||||
hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset + 0).GetHashCode());
|
||||
hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset + 1).GetHashCode());
|
||||
hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset + 2).GetHashCode());
|
||||
hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset + 3).GetHashCode());
|
||||
hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset + 4).GetHashCode());
|
||||
hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset + 5).GetHashCode());
|
||||
hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset + 6).GetHashCode());
|
||||
hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset + 7).GetHashCode());
|
||||
|
||||
length -= 8;
|
||||
offset += 8;
|
||||
}
|
||||
|
||||
if ((byte*)length >= (byte*)4)
|
||||
{
|
||||
hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset + 0).GetHashCode());
|
||||
hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset + 1).GetHashCode());
|
||||
hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset + 2).GetHashCode());
|
||||
hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset + 3).GetHashCode());
|
||||
|
||||
length -= 4;
|
||||
offset += 4;
|
||||
}
|
||||
|
||||
while ((byte*)length > (byte*)0)
|
||||
{
|
||||
hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset).GetHashCode());
|
||||
|
||||
length -= 1;
|
||||
offset += 1;
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a content hash from a given memory area.
|
||||
/// </summary>
|
||||
/// <param name="r0">A <see cref="byte"/> reference to the start of the memory area.</param>
|
||||
/// <param name="length">The size in bytes of the memory area.</param>
|
||||
/// <returns>The hash code for the contents of the source memory area.</returns>
|
||||
/// <remarks>
|
||||
/// While this method is similar to <see cref="GetDjb2HashCode{T}"/> and can in some cases
|
||||
/// produce the same output for a given memory area, it is not guaranteed to always be that way.
|
||||
/// This is because this method can use SIMD instructions if possible, which can cause a computed
|
||||
/// hash to differ for the same data, if processed on different machines with different CPU features.
|
||||
/// The advantage of this method is that when SIMD instructions are available, it performs much
|
||||
/// faster than <see cref="GetDjb2HashCode{T}"/>, as it can parallelize much of the workload.
|
||||
/// </remarks>
|
||||
[Pure]
|
||||
#if NETCOREAPP3_1
|
||||
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
||||
#endif
|
||||
public static unsafe int GetDjb2LikeByteHash(ref byte r0, IntPtr length)
|
||||
{
|
||||
int hash = 5381;
|
||||
|
||||
IntPtr offset = default;
|
||||
|
||||
// Check whether SIMD instructions are supported, and also check
|
||||
// whether we have enough data to perform at least one unrolled
|
||||
// iteration of the vectorized path. This heuristics is to balance
|
||||
// the overhead of loading the constant values in the two registers,
|
||||
// and the final loop to combine the partial hash values.
|
||||
// Note that even when we use the vectorized path we don't need to do
|
||||
// any preprocessing to try to get memory aligned, as that would cause
|
||||
// the hashcodes to potentially be different for the same data.
|
||||
if (Vector.IsHardwareAccelerated &&
|
||||
(byte*)length >= (byte*)(Vector<byte>.Count << 3))
|
||||
{
|
||||
var vh = new Vector<int>(5381);
|
||||
var v33 = new Vector<int>(33);
|
||||
|
||||
// First vectorized loop, with 8 unrolled iterations.
|
||||
// Assuming 256-bit registers (AVX2), a total of 256 bytes are processed
|
||||
// per iteration, with the partial hashes being accumulated for later use.
|
||||
while ((byte*)length >= (byte*)(Vector<byte>.Count << 3))
|
||||
{
|
||||
ref byte ri0 = ref Unsafe.Add(ref r0, offset + (Vector<byte>.Count * 0));
|
||||
var vi0 = Unsafe.ReadUnaligned<Vector<int>>(ref ri0);
|
||||
var vp0 = Vector.Multiply(vh, v33);
|
||||
vh = Vector.Xor(vp0, vi0);
|
||||
|
||||
ref byte ri1 = ref Unsafe.Add(ref r0, offset + (Vector<byte>.Count * 1));
|
||||
var vi1 = Unsafe.ReadUnaligned<Vector<int>>(ref ri1);
|
||||
var vp1 = Vector.Multiply(vh, v33);
|
||||
vh = Vector.Xor(vp1, vi1);
|
||||
|
||||
ref byte ri2 = ref Unsafe.Add(ref r0, offset + (Vector<byte>.Count * 2));
|
||||
var vi2 = Unsafe.ReadUnaligned<Vector<int>>(ref ri2);
|
||||
var vp2 = Vector.Multiply(vh, v33);
|
||||
vh = Vector.Xor(vp2, vi2);
|
||||
|
||||
ref byte ri3 = ref Unsafe.Add(ref r0, offset + (Vector<byte>.Count * 3));
|
||||
var vi3 = Unsafe.ReadUnaligned<Vector<int>>(ref ri3);
|
||||
var vp3 = Vector.Multiply(vh, v33);
|
||||
vh = Vector.Xor(vp3, vi3);
|
||||
|
||||
ref byte ri4 = ref Unsafe.Add(ref r0, offset + (Vector<byte>.Count * 4));
|
||||
var vi4 = Unsafe.ReadUnaligned<Vector<int>>(ref ri4);
|
||||
var vp4 = Vector.Multiply(vh, v33);
|
||||
vh = Vector.Xor(vp4, vi4);
|
||||
|
||||
ref byte ri5 = ref Unsafe.Add(ref r0, offset + (Vector<byte>.Count * 5));
|
||||
var vi5 = Unsafe.ReadUnaligned<Vector<int>>(ref ri5);
|
||||
var vp5 = Vector.Multiply(vh, v33);
|
||||
vh = Vector.Xor(vp5, vi5);
|
||||
|
||||
ref byte ri6 = ref Unsafe.Add(ref r0, offset + (Vector<byte>.Count * 6));
|
||||
var vi6 = Unsafe.ReadUnaligned<Vector<int>>(ref ri6);
|
||||
var vp6 = Vector.Multiply(vh, v33);
|
||||
vh = Vector.Xor(vp6, vi6);
|
||||
|
||||
ref byte ri7 = ref Unsafe.Add(ref r0, offset + (Vector<byte>.Count * 7));
|
||||
var vi7 = Unsafe.ReadUnaligned<Vector<int>>(ref ri7);
|
||||
var vp7 = Vector.Multiply(vh, v33);
|
||||
vh = Vector.Xor(vp7, vi7);
|
||||
|
||||
length -= Vector<byte>.Count << 3;
|
||||
offset += Vector<byte>.Count << 3;
|
||||
}
|
||||
|
||||
// When this loop is reached, there are up to 255 bytes left (on AVX2).
|
||||
// Each iteration processed an additional 32 bytes and accumulates the results.
|
||||
while ((byte*)length >= (byte*)Vector<byte>.Count)
|
||||
{
|
||||
ref byte ri = ref Unsafe.Add(ref r0, offset);
|
||||
var vi = Unsafe.ReadUnaligned<Vector<int>>(ref ri);
|
||||
var vp = Vector.Multiply(vh, v33);
|
||||
vh = Vector.Xor(vp, vi);
|
||||
|
||||
length -= Vector<byte>.Count;
|
||||
offset += Vector<byte>.Count;
|
||||
}
|
||||
|
||||
// Combine the partial hash values in each position.
|
||||
// The loop below should automatically be unrolled by the JIT.
|
||||
for (var j = 0; j < Vector<int>.Count; j++)
|
||||
{
|
||||
hash = unchecked(((hash << 5) + hash) ^ vh[j]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Only use the loop working with 64-bit values if we are on a
|
||||
// 64-bit processor, otherwise the result would be much slower.
|
||||
// Each unrolled iteration processes 64 bytes.
|
||||
if (sizeof(IntPtr) == sizeof(ulong))
|
||||
{
|
||||
while ((byte*)length >= (byte*)(sizeof(ulong) << 3))
|
||||
{
|
||||
ref byte ri0 = ref Unsafe.Add(ref r0, offset + (sizeof(ulong) * 0));
|
||||
var value0 = Unsafe.ReadUnaligned<ulong>(ref ri0);
|
||||
hash = unchecked(((hash << 5) + hash) ^ (int)value0 ^ (int)(value0 >> 32));
|
||||
|
||||
ref byte ri1 = ref Unsafe.Add(ref r0, offset + (sizeof(ulong) * 1));
|
||||
var value1 = Unsafe.ReadUnaligned<ulong>(ref ri1);
|
||||
hash = unchecked(((hash << 5) + hash) ^ (int)value1 ^ (int)(value1 >> 32));
|
||||
|
||||
ref byte ri2 = ref Unsafe.Add(ref r0, offset + (sizeof(ulong) * 2));
|
||||
var value2 = Unsafe.ReadUnaligned<ulong>(ref ri2);
|
||||
hash = unchecked(((hash << 5) + hash) ^ (int)value2 ^ (int)(value2 >> 32));
|
||||
|
||||
ref byte ri3 = ref Unsafe.Add(ref r0, offset + (sizeof(ulong) * 3));
|
||||
var value3 = Unsafe.ReadUnaligned<ulong>(ref ri3);
|
||||
hash = unchecked(((hash << 5) + hash) ^ (int)value3 ^ (int)(value3 >> 32));
|
||||
|
||||
ref byte ri4 = ref Unsafe.Add(ref r0, offset + (sizeof(ulong) * 4));
|
||||
var value4 = Unsafe.ReadUnaligned<ulong>(ref ri4);
|
||||
hash = unchecked(((hash << 5) + hash) ^ (int)value4 ^ (int)(value4 >> 32));
|
||||
|
||||
ref byte ri5 = ref Unsafe.Add(ref r0, offset + (sizeof(ulong) * 5));
|
||||
var value5 = Unsafe.ReadUnaligned<ulong>(ref ri5);
|
||||
hash = unchecked(((hash << 5) + hash) ^ (int)value5 ^ (int)(value5 >> 32));
|
||||
|
||||
ref byte ri6 = ref Unsafe.Add(ref r0, offset + (sizeof(ulong) * 6));
|
||||
var value6 = Unsafe.ReadUnaligned<ulong>(ref ri6);
|
||||
hash = unchecked(((hash << 5) + hash) ^ (int)value6 ^ (int)(value6 >> 32));
|
||||
|
||||
ref byte ri7 = ref Unsafe.Add(ref r0, offset + (sizeof(ulong) * 7));
|
||||
var value7 = Unsafe.ReadUnaligned<ulong>(ref ri7);
|
||||
hash = unchecked(((hash << 5) + hash) ^ (int)value7 ^ (int)(value7 >> 32));
|
||||
|
||||
length -= sizeof(ulong) << 3;
|
||||
offset += sizeof(ulong) << 3;
|
||||
}
|
||||
}
|
||||
|
||||
// Each unrolled iteration processes 32 bytes
|
||||
while ((byte*)length >= (byte*)(sizeof(uint) << 3))
|
||||
{
|
||||
ref byte ri0 = ref Unsafe.Add(ref r0, offset + (sizeof(uint) * 0));
|
||||
var value0 = Unsafe.ReadUnaligned<uint>(ref ri0);
|
||||
hash = unchecked(((hash << 5) + hash) ^ (int)value0);
|
||||
|
||||
ref byte ri1 = ref Unsafe.Add(ref r0, offset + (sizeof(uint) * 1));
|
||||
var value1 = Unsafe.ReadUnaligned<uint>(ref ri1);
|
||||
hash = unchecked(((hash << 5) + hash) ^ (int)value1);
|
||||
|
||||
ref byte ri2 = ref Unsafe.Add(ref r0, offset + (sizeof(uint) * 2));
|
||||
var value2 = Unsafe.ReadUnaligned<uint>(ref ri2);
|
||||
hash = unchecked(((hash << 5) + hash) ^ (int)value2);
|
||||
|
||||
ref byte ri3 = ref Unsafe.Add(ref r0, offset + (sizeof(uint) * 3));
|
||||
var value3 = Unsafe.ReadUnaligned<uint>(ref ri3);
|
||||
hash = unchecked(((hash << 5) + hash) ^ (int)value3);
|
||||
|
||||
ref byte ri4 = ref Unsafe.Add(ref r0, offset + (sizeof(uint) * 4));
|
||||
var value4 = Unsafe.ReadUnaligned<uint>(ref ri4);
|
||||
hash = unchecked(((hash << 5) + hash) ^ (int)value4);
|
||||
|
||||
ref byte ri5 = ref Unsafe.Add(ref r0, offset + (sizeof(uint) * 5));
|
||||
var value5 = Unsafe.ReadUnaligned<uint>(ref ri5);
|
||||
hash = unchecked(((hash << 5) + hash) ^ (int)value5);
|
||||
|
||||
ref byte ri6 = ref Unsafe.Add(ref r0, offset + (sizeof(uint) * 6));
|
||||
var value6 = Unsafe.ReadUnaligned<uint>(ref ri6);
|
||||
hash = unchecked(((hash << 5) + hash) ^ (int)value6);
|
||||
|
||||
ref byte ri7 = ref Unsafe.Add(ref r0, offset + (sizeof(uint) * 7));
|
||||
var value7 = Unsafe.ReadUnaligned<uint>(ref ri7);
|
||||
hash = unchecked(((hash << 5) + hash) ^ (int)value7);
|
||||
|
||||
length -= sizeof(uint) << 3;
|
||||
offset += sizeof(uint) << 3;
|
||||
}
|
||||
}
|
||||
|
||||
// At this point (assuming AVX2), there will be up to 31 bytes
|
||||
// left, both for the vectorized and non vectorized paths.
|
||||
// That number would go up to 63 on AVX512 systems, in which case it is
|
||||
// still useful to perform this last loop unrolling.
|
||||
if ((byte*)length >= (byte*)(sizeof(ushort) << 3))
|
||||
{
|
||||
ref byte ri0 = ref Unsafe.Add(ref r0, offset + (sizeof(ushort) * 0));
|
||||
var value0 = Unsafe.ReadUnaligned<ushort>(ref ri0);
|
||||
hash = unchecked(((hash << 5) + hash) ^ value0);
|
||||
|
||||
ref byte ri1 = ref Unsafe.Add(ref r0, offset + (sizeof(ushort) * 1));
|
||||
var value1 = Unsafe.ReadUnaligned<ushort>(ref ri1);
|
||||
hash = unchecked(((hash << 5) + hash) ^ value1);
|
||||
|
||||
ref byte ri2 = ref Unsafe.Add(ref r0, offset + (sizeof(ushort) * 2));
|
||||
var value2 = Unsafe.ReadUnaligned<ushort>(ref ri2);
|
||||
hash = unchecked(((hash << 5) + hash) ^ value2);
|
||||
|
||||
ref byte ri3 = ref Unsafe.Add(ref r0, offset + (sizeof(ushort) * 3));
|
||||
var value3 = Unsafe.ReadUnaligned<ushort>(ref ri3);
|
||||
hash = unchecked(((hash << 5) + hash) ^ value3);
|
||||
|
||||
ref byte ri4 = ref Unsafe.Add(ref r0, offset + (sizeof(ushort) * 4));
|
||||
var value4 = Unsafe.ReadUnaligned<ushort>(ref ri4);
|
||||
hash = unchecked(((hash << 5) + hash) ^ value4);
|
||||
|
||||
ref byte ri5 = ref Unsafe.Add(ref r0, offset + (sizeof(ushort) * 5));
|
||||
var value5 = Unsafe.ReadUnaligned<ushort>(ref ri5);
|
||||
hash = unchecked(((hash << 5) + hash) ^ value5);
|
||||
|
||||
ref byte ri6 = ref Unsafe.Add(ref r0, offset + (sizeof(ushort) * 6));
|
||||
var value6 = Unsafe.ReadUnaligned<ushort>(ref ri6);
|
||||
hash = unchecked(((hash << 5) + hash) ^ value6);
|
||||
|
||||
ref byte ri7 = ref Unsafe.Add(ref r0, offset + (sizeof(ushort) * 7));
|
||||
var value7 = Unsafe.ReadUnaligned<ushort>(ref ri7);
|
||||
hash = unchecked(((hash << 5) + hash) ^ value7);
|
||||
|
||||
length -= sizeof(ushort) << 3;
|
||||
offset += sizeof(ushort) << 3;
|
||||
}
|
||||
|
||||
// Handle the leftover items
|
||||
while ((byte*)length > (byte*)0)
|
||||
{
|
||||
hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset));
|
||||
|
||||
length -= 1;
|
||||
offset += 1;
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,7 +13,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
|
|||
/// </summary>
|
||||
public static partial class ParallelHelper
|
||||
{
|
||||
#if NETSTANDARD2_1
|
||||
#if NETSTANDARD2_1_OR_GREATER
|
||||
/// <summary>
|
||||
/// Executes a specified action in an optimized parallel loop.
|
||||
/// </summary>
|
||||
|
@ -53,7 +53,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
|
|||
/// <param name="action">The <typeparamref name="TAction"/> instance representing the action to invoke.</param>
|
||||
/// <remarks>None of the bounds of <paramref name="range"/> can start from an end.</remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void For<TAction>(Range range, TAction action)
|
||||
public static void For<TAction>(Range range, in TAction action)
|
||||
where TAction : struct, IAction
|
||||
{
|
||||
For(range, action, 1);
|
||||
|
@ -72,7 +72,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
|
|||
/// </param>
|
||||
/// <remarks>None of the bounds of <paramref name="range"/> can start from an end.</remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void For<TAction>(Range range, TAction action, int minimumActionsPerThread)
|
||||
public static void For<TAction>(Range range, in TAction action, int minimumActionsPerThread)
|
||||
where TAction : struct, IAction
|
||||
{
|
||||
if (range.Start.IsFromEnd || range.End.IsFromEnd)
|
||||
|
|
|
@ -14,7 +14,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
|
|||
/// </summary>
|
||||
public static partial class ParallelHelper
|
||||
{
|
||||
#if NETSTANDARD2_1
|
||||
#if NETSTANDARD2_1_OR_GREATER
|
||||
/// <summary>
|
||||
/// Executes a specified action in an optimized parallel loop.
|
||||
/// </summary>
|
||||
|
|
|
@ -18,10 +18,10 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
|
|||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void ThrowArgumentOutOfRangeExceptionForInvalidMinimumActionsPerThread()
|
||||
{
|
||||
/* Having the argument name here manually typed is
|
||||
* not ideal, but this way we save passing that string as
|
||||
* a parameter, since it's always the same anyway.
|
||||
* Same goes for the other helper methods below. */
|
||||
// Having the argument name here manually typed is
|
||||
// not ideal, but this way we save passing that string as
|
||||
// a parameter, since it's always the same anyway.
|
||||
// Same goes for the other helper methods below.
|
||||
throw new ArgumentOutOfRangeException(
|
||||
"minimumActionsPerThread",
|
||||
"Each thread needs to perform at least one action");
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
|
||||
<TargetFrameworks>netstandard1.4;netstandard2.0;netstandard2.1;netcoreapp2.1;netcoreapp3.1</TargetFrameworks>
|
||||
<LangVersion>8.0</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<Title>Windows Community Toolkit High Performance .NET Standard</Title>
|
||||
<Description>
|
||||
This package includes high performance .NET Standard helpers such as:
|
||||
- ArrayPoolBufferWriter<T>: an IBufferWriter<T> implementation using pooled arrays, which also supports IMemoryOwner<T>.
|
||||
- MemoryBufferWriter<T>: an IBufferWriter<T>: implementation that can wrap external Memory<T>: instances.
|
||||
- MemoryOwner<T>: an IMemoryOwner<T> implementation with an embedded length and a fast Span<T> accessor.
|
||||
- SpanOwner<T>: a stack-only type with the ability to rent a buffer of a specified length and getting a Span<T> from it.
|
||||
- String, array, Span<T>, Memory<T> extensions and more, all focused on high performance.
|
||||
|
@ -16,23 +18,84 @@
|
|||
- ParallelHelper: helpers to work with parallel code in a highly optimized manner.
|
||||
- Box<T>: a type mapping boxed value types and exposing some utility and high performance methods.
|
||||
- Ref<T>: a stack-only struct that can store a reference to a value of a specified type.
|
||||
</Description>
|
||||
<PackageTags>UWP Toolkit Windows IncrementalLoadingCollection String Array extensions helpers</PackageTags>
|
||||
- NullableRef<T>: a stack-only struct similar to Ref<T>, which also supports nullable references.
|
||||
</Description>
|
||||
<PackageTags>UWP Toolkit Windows core standard unsafe span memory string array stream buffer extensions helpers parallel performance</PackageTags>
|
||||
|
||||
<!-- This is a temporary workaround for https://github.com/dotnet/sdk/issues/955 -->
|
||||
<DebugType>Full</DebugType>
|
||||
</PropertyGroup>
|
||||
<Choose>
|
||||
<When Condition=" '$(TargetFramework)' == 'netstandard1.4' ">
|
||||
<ItemGroup>
|
||||
|
||||
<!-- .NET Standard 2.0 doesn't have the Span<T> and HashCode types -->
|
||||
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
|
||||
<PackageReference Include="Microsoft.Bcl.HashCode" Version="1.1.0" />
|
||||
<PackageReference Include="System.Memory" Version="4.5.4" />
|
||||
</ItemGroup>
|
||||
<!-- .NET Standard 1.4 lacks the [Pure] attribute, the Rectangle primitive,
|
||||
the Span<T> and Memory<T> types, the Vector<T> primitive and related APIs,
|
||||
ValueTask and ValueTask<T>, the Parallel class and the Unsafe class.
|
||||
We also need to reference the System.Runtime.CompilerServices.Unsafe package directly,
|
||||
even though System.Memory references it already, as we need a more recent version than
|
||||
the one bundled with it. This is so that we can use the Unsafe.Unbox<T> method,
|
||||
which is used by the Box<T> type in the package. -->
|
||||
<PackageReference Include="System.Diagnostics.Contracts" Version="4.3.0" />
|
||||
<PackageReference Include="System.Drawing.Primitives" Version="4.3.0" />
|
||||
<PackageReference Include="System.Memory" Version="4.5.4" />
|
||||
<PackageReference Include="System.Numerics.Vectors" Version="4.5.0" />
|
||||
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.4" />
|
||||
<PackageReference Include="System.Threading.Tasks.Parallel" Version="4.3.0" />
|
||||
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.7.1" />
|
||||
</ItemGroup>
|
||||
</When>
|
||||
<When Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
|
||||
<ItemGroup>
|
||||
|
||||
<!-- Always import the Unsafe type even on .NET Standard 2.1, so that the 5.0.0 version can be used.
|
||||
This is necessary to be able to use the Unsafe.Unbox<T>(object) API, which is otherwise missing. -->
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.7.1" />
|
||||
</ItemGroup>
|
||||
<!-- .NET Standard 2.0 doesn't have the Span<T>, HashCode and ValueTask types -->
|
||||
<PackageReference Include="Microsoft.Bcl.HashCode" Version="1.1.0" />
|
||||
<PackageReference Include="System.Memory" Version="4.5.4" />
|
||||
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.4" />
|
||||
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.7.1" />
|
||||
</ItemGroup>
|
||||
</When>
|
||||
<When Condition=" '$(TargetFramework)' == 'netstandard2.1' ">
|
||||
<ItemGroup>
|
||||
|
||||
<!-- .NET Standard 2.1 doesn't have the Unsafe type -->
|
||||
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.7.1" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
|
||||
<!-- NETSTANDARD2_1_OR_GREATER: includes both .NET Standard 2.1 and .NET Core 3.1.
|
||||
This is needed because .NET Core 3.1 will be a separate package than .NET Standard 2.1. -->
|
||||
|
||||
<!-- SPAN_RUNTIME_SUPPORT: define a constant to indicate runtimes with runtime support for
|
||||
the fast Span<T> type, as well as some overloads with Span<T> parameters (eg. Stream.Write).
|
||||
In particular, these are runtimes which are able to create Span<T> instances from just
|
||||
a managed reference, which can be used to slice arbitrary objects not technically supported.
|
||||
This API (MemoryMarshal.CreateSpan) is not part of .NET Standard 2.0, but it is still
|
||||
available on .NET Core 2.1. So by using this constant, we can make sure to expose those
|
||||
APIs relying on that method on all target frameworks that are able to support them. -->
|
||||
<DefineConstants>NETSTANDARD2_1_OR_GREATER;SPAN_RUNTIME_SUPPORT</DefineConstants>
|
||||
</PropertyGroup>
|
||||
</When>
|
||||
<When Condition=" '$(TargetFramework)' == 'netcoreapp3.1' ">
|
||||
<PropertyGroup>
|
||||
|
||||
<!-- 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
|
||||
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
|
||||
actually used by the consumers. -->
|
||||
<DefineConstants>NETSTANDARD2_1_OR_GREATER;SPAN_RUNTIME_SUPPORT;NETCORE_RUNTIME</DefineConstants>
|
||||
</PropertyGroup>
|
||||
</When>
|
||||
<When Condition=" '$(TargetFramework)' == 'netcoreapp2.1' ">
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.7.1" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<DefineConstants>SPAN_RUNTIME_SUPPORT;NETCORE_RUNTIME</DefineConstants>
|
||||
</PropertyGroup>
|
||||
</When>
|
||||
</Choose>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -2,10 +2,9 @@
|
|||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
#if NETSTANDARD2_1
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
|
@ -15,7 +14,6 @@ namespace Microsoft.Toolkit.HighPerformance
|
|||
/// A <see langword="struct"/> that can store an optional readonly reference to a value of a specified type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of value to reference.</typeparam>
|
||||
[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1206", Justification = "The type is a ref struct")]
|
||||
public readonly ref struct NullableReadOnlyRef<T>
|
||||
{
|
||||
/// <summary>
|
||||
|
@ -32,7 +30,26 @@ namespace Microsoft.Toolkit.HighPerformance
|
|||
{
|
||||
ref T r0 = ref Unsafe.AsRef(value);
|
||||
|
||||
span = MemoryMarshal.CreateReadOnlySpan(ref r0, 1);
|
||||
this.span = MemoryMarshal.CreateReadOnlySpan(ref r0, 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NullableReadOnlyRef{T}"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="span">The <see cref="ReadOnlySpan{T}"/> instance to track the target <typeparamref name="T"/> reference.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private NullableReadOnlyRef(ReadOnlySpan<T> span)
|
||||
{
|
||||
this.span = span;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="NullableReadOnlyRef{T}"/> instance representing a <see langword="null"/> reference.
|
||||
/// </summary>
|
||||
public static NullableReadOnlyRef<T> Null
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -44,7 +61,7 @@ namespace Microsoft.Toolkit.HighPerformance
|
|||
get
|
||||
{
|
||||
// See comment in NullableRef<T> about this
|
||||
byte length = unchecked((byte)span.Length);
|
||||
byte length = unchecked((byte)this.span.Length);
|
||||
|
||||
return Unsafe.As<byte, bool>(ref length);
|
||||
}
|
||||
|
@ -53,7 +70,7 @@ namespace Microsoft.Toolkit.HighPerformance
|
|||
/// <summary>
|
||||
/// Gets the <typeparamref name="T"/> reference represented by the current <see cref="NullableReadOnlyRef{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <exception cref="NullReferenceException">Thrown if <see cref="HasValue"/> is <see langword="false"/>.</exception>
|
||||
/// <exception cref="InvalidOperationException">Thrown if <see cref="HasValue"/> is <see langword="false"/>.</exception>
|
||||
public ref readonly T Value
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
|
@ -61,20 +78,61 @@ namespace Microsoft.Toolkit.HighPerformance
|
|||
{
|
||||
if (!HasValue)
|
||||
{
|
||||
ThrowNullReferenceException();
|
||||
ThrowInvalidOperationException();
|
||||
}
|
||||
|
||||
return ref MemoryMarshal.GetReference(span);
|
||||
return ref MemoryMarshal.GetReference(this.span);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a <see cref="NullReferenceException"/> when trying to access <see cref="Value"/> for a default instance.
|
||||
/// Implicitly converts a <see cref="Ref{T}"/> instance into a <see cref="NullableReadOnlyRef{T}"/> one.
|
||||
/// </summary>
|
||||
/// <param name="reference">The input <see cref="Ref{T}"/> instance.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static implicit operator NullableReadOnlyRef<T>(Ref<T> reference)
|
||||
{
|
||||
return new NullableReadOnlyRef<T>(reference.Span);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly converts a <see cref="ReadOnlyRef{T}"/> instance into a <see cref="NullableReadOnlyRef{T}"/> one.
|
||||
/// </summary>
|
||||
/// <param name="reference">The input <see cref="ReadOnlyRef{T}"/> instance.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static implicit operator NullableReadOnlyRef<T>(ReadOnlyRef<T> reference)
|
||||
{
|
||||
return new NullableReadOnlyRef<T>(reference.Span);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly converts a <see cref="NullableRef{T}"/> instance into a <see cref="NullableReadOnlyRef{T}"/> one.
|
||||
/// </summary>
|
||||
/// <param name="reference">The input <see cref="Ref{T}"/> instance.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static implicit operator NullableReadOnlyRef<T>(NullableRef<T> reference)
|
||||
{
|
||||
return new NullableReadOnlyRef<T>(reference.Span);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Explicitly gets the <typeparamref name="T"/> value from a given <see cref="NullableReadOnlyRef{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="reference">The input <see cref="NullableReadOnlyRef{T}"/> instance.</param>
|
||||
/// <exception cref="InvalidOperationException">Thrown if <see cref="HasValue"/> is <see langword="false"/>.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static explicit operator T(NullableReadOnlyRef<T> reference)
|
||||
{
|
||||
return reference.Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a <see cref="InvalidOperationException"/> when trying to access <see cref="Value"/> for a default instance.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void ThrowNullReferenceException()
|
||||
private static void ThrowInvalidOperationException()
|
||||
{
|
||||
throw new NullReferenceException("The current instance doesn't have a value that can be accessed");
|
||||
throw new InvalidOperationException("The current instance doesn't have a value that can be accessed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,10 +2,9 @@
|
|||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
#if NETSTANDARD2_1
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
|
@ -15,13 +14,12 @@ namespace Microsoft.Toolkit.HighPerformance
|
|||
/// A <see langword="struct"/> that can store an optional reference to a value of a specified type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of value to reference.</typeparam>
|
||||
[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1206", Justification = "The type is a ref struct")]
|
||||
public readonly ref struct NullableRef<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// The 1-length <see cref="Span{T}"/> instance used to track the target <typeparamref name="T"/> value.
|
||||
/// </summary>
|
||||
private readonly Span<T> span;
|
||||
internal readonly Span<T> Span;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NullableRef{T}"/> struct.
|
||||
|
@ -30,7 +28,26 @@ namespace Microsoft.Toolkit.HighPerformance
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public NullableRef(ref T value)
|
||||
{
|
||||
span = MemoryMarshal.CreateSpan(ref value, 1);
|
||||
Span = MemoryMarshal.CreateSpan(ref value, 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NullableRef{T}"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="span">The <see cref="Span{T}"/> instance to track the target <typeparamref name="T"/> reference.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private NullableRef(Span<T> span)
|
||||
{
|
||||
Span = span;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="NullableRef{T}"/> instance representing a <see langword="null"/> reference.
|
||||
/// </summary>
|
||||
public static NullableRef<T> Null
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -41,14 +58,14 @@ namespace Microsoft.Toolkit.HighPerformance
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
/* We know that the span will always have a length of either
|
||||
* 1 or 0, se instead of using a cmp instruction and setting the
|
||||
* zero flag to produce our boolean value, we can just cast
|
||||
* the length to byte without overflow checks (doing a cast will
|
||||
* also account for the byte endianness of the current system),
|
||||
* and then reinterpret that value to a bool flag.
|
||||
* This results in a single movzx instruction on x86-64. */
|
||||
byte length = unchecked((byte)span.Length);
|
||||
// We know that the span will always have a length of either
|
||||
// 1 or 0, se instead of using a cmp instruction and setting the
|
||||
// zero flag to produce our boolean value, we can just cast
|
||||
// the length to byte without overflow checks (doing a cast will
|
||||
// also account for the byte endianness of the current system),
|
||||
// and then reinterpret that value to a bool flag.
|
||||
// This results in a single movzx instruction on x86-64.
|
||||
byte length = unchecked((byte)Span.Length);
|
||||
|
||||
return Unsafe.As<byte, bool>(ref length);
|
||||
}
|
||||
|
@ -57,7 +74,7 @@ namespace Microsoft.Toolkit.HighPerformance
|
|||
/// <summary>
|
||||
/// Gets the <typeparamref name="T"/> reference represented by the current <see cref="NullableRef{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <exception cref="NullReferenceException">Thrown if <see cref="HasValue"/> is <see langword="false"/>.</exception>
|
||||
/// <exception cref="InvalidOperationException">Thrown if <see cref="HasValue"/> is <see langword="false"/>.</exception>
|
||||
public ref T Value
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
|
@ -65,20 +82,41 @@ namespace Microsoft.Toolkit.HighPerformance
|
|||
{
|
||||
if (!HasValue)
|
||||
{
|
||||
ThrowNullReferenceException();
|
||||
ThrowInvalidOperationException();
|
||||
}
|
||||
|
||||
return ref MemoryMarshal.GetReference(span);
|
||||
return ref MemoryMarshal.GetReference(Span);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a <see cref="NullReferenceException"/> when trying to access <see cref="Value"/> for a default instance.
|
||||
/// Implicitly converts a <see cref="Ref{T}"/> instance into a <see cref="NullableRef{T}"/> one.
|
||||
/// </summary>
|
||||
/// <param name="reference">The input <see cref="Ref{T}"/> instance.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static implicit operator NullableRef<T>(Ref<T> reference)
|
||||
{
|
||||
return new NullableRef<T>(reference.Span);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Explicitly gets the <typeparamref name="T"/> value from a given <see cref="NullableRef{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="reference">The input <see cref="NullableRef{T}"/> instance.</param>
|
||||
/// <exception cref="InvalidOperationException">Thrown if <see cref="HasValue"/> is <see langword="false"/>.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static explicit operator T(NullableRef<T> reference)
|
||||
{
|
||||
return reference.Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a <see cref="InvalidOperationException"/> when trying to access <see cref="Value"/> for a default instance.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void ThrowNullReferenceException()
|
||||
private static void ThrowInvalidOperationException()
|
||||
{
|
||||
throw new NullReferenceException("The current instance doesn't have a value that can be accessed");
|
||||
throw new InvalidOperationException("The current instance doesn't have a value that can be accessed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,10 +3,11 @@
|
|||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
#if NETSTANDARD2_1
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
using System.Runtime.InteropServices;
|
||||
#else
|
||||
using Microsoft.Toolkit.HighPerformance.Extensions;
|
||||
#endif
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance
|
||||
|
@ -15,14 +16,13 @@ namespace Microsoft.Toolkit.HighPerformance
|
|||
/// A <see langword="struct"/> that can store a readonly reference to a value of a specified type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of value to reference.</typeparam>
|
||||
[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1206", Justification = "The type is a ref struct")]
|
||||
public readonly ref struct ReadOnlyRef<T>
|
||||
{
|
||||
#if NETSTANDARD2_1
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
/// <summary>
|
||||
/// The 1-length <see cref="ReadOnlySpan{T}"/> instance used to track the target <typeparamref name="T"/> value.
|
||||
/// </summary>
|
||||
private readonly ReadOnlySpan<T> span;
|
||||
internal readonly ReadOnlySpan<T> Span;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ReadOnlyRef{T}"/> struct.
|
||||
|
@ -33,7 +33,7 @@ namespace Microsoft.Toolkit.HighPerformance
|
|||
{
|
||||
ref T r0 = ref Unsafe.AsRef(value);
|
||||
|
||||
span = MemoryMarshal.CreateReadOnlySpan(ref r0, 1);
|
||||
Span = MemoryMarshal.CreateReadOnlySpan(ref r0, 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -42,7 +42,7 @@ namespace Microsoft.Toolkit.HighPerformance
|
|||
public ref readonly T Value
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => ref MemoryMarshal.GetReference(span);
|
||||
get => ref MemoryMarshal.GetReference(Span);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -88,13 +88,7 @@ namespace Microsoft.Toolkit.HighPerformance
|
|||
public ReadOnlyRef(object owner, in T value)
|
||||
{
|
||||
this.owner = owner;
|
||||
|
||||
ref T valueRef = ref Unsafe.AsRef(value);
|
||||
var data = Unsafe.As<RawObjectData>(owner);
|
||||
ref byte r0 = ref data.Data;
|
||||
ref byte r1 = ref Unsafe.As<T, byte>(ref valueRef);
|
||||
|
||||
offset = Unsafe.ByteOffset(ref r0, ref r1);
|
||||
this.offset = owner.DangerousGetObjectDataByteOffset(ref Unsafe.AsRef(value));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -103,14 +97,7 @@ namespace Microsoft.Toolkit.HighPerformance
|
|||
public ref readonly T Value
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
var data = Unsafe.As<RawObjectData>(owner);
|
||||
ref byte r0 = ref data.Data;
|
||||
ref byte r1 = ref Unsafe.AddByteOffset(ref r0, offset);
|
||||
|
||||
return ref Unsafe.As<byte, T>(ref r1);
|
||||
}
|
||||
get => ref this.owner.DangerousGetObjectDataReferenceAt<T>(this.offset);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -3,9 +3,12 @@
|
|||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
using System.Runtime.InteropServices;
|
||||
#else
|
||||
using Microsoft.Toolkit.HighPerformance.Extensions;
|
||||
#endif
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance
|
||||
{
|
||||
|
@ -13,14 +16,13 @@ namespace Microsoft.Toolkit.HighPerformance
|
|||
/// A <see langword="struct"/> that can store a reference to a value of a specified type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of value to reference.</typeparam>
|
||||
[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1206", Justification = "The type is a ref struct")]
|
||||
public readonly ref struct Ref<T>
|
||||
{
|
||||
#if NETSTANDARD2_1
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
/// <summary>
|
||||
/// The 1-length <see cref="Span{T}"/> instance used to track the target <typeparamref name="T"/> value.
|
||||
/// </summary>
|
||||
private readonly Span<T> span;
|
||||
internal readonly Span<T> Span;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Ref{T}"/> struct.
|
||||
|
@ -29,7 +31,7 @@ namespace Microsoft.Toolkit.HighPerformance
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Ref(ref T value)
|
||||
{
|
||||
span = MemoryMarshal.CreateSpan(ref value, 1);
|
||||
Span = MemoryMarshal.CreateSpan(ref value, 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -38,7 +40,7 @@ namespace Microsoft.Toolkit.HighPerformance
|
|||
public ref T Value
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => ref MemoryMarshal.GetReference(span);
|
||||
get => ref MemoryMarshal.GetReference(Span);
|
||||
}
|
||||
#else
|
||||
/// <summary>
|
||||
|
@ -64,13 +66,8 @@ namespace Microsoft.Toolkit.HighPerformance
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Ref(object owner, ref T value)
|
||||
{
|
||||
this.Owner = owner;
|
||||
|
||||
var data = Unsafe.As<RawObjectData>(owner);
|
||||
ref byte r0 = ref data.Data;
|
||||
ref byte r1 = ref Unsafe.As<T, byte>(ref value);
|
||||
|
||||
Offset = Unsafe.ByteOffset(ref r0, ref r1);
|
||||
Owner = owner;
|
||||
Offset = owner.DangerousGetObjectDataByteOffset(ref value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -79,14 +76,7 @@ namespace Microsoft.Toolkit.HighPerformance
|
|||
public ref T Value
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
var data = Unsafe.As<RawObjectData>(Owner);
|
||||
ref byte r0 = ref data.Data;
|
||||
ref byte r1 = ref Unsafe.AddByteOffset(ref r0, Offset);
|
||||
|
||||
return ref Unsafe.As<byte, T>(ref r1);
|
||||
}
|
||||
get => ref Owner.DangerousGetObjectDataReferenceAt<T>(Offset);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -100,24 +90,4 @@ namespace Microsoft.Toolkit.HighPerformance
|
|||
return reference.Value;
|
||||
}
|
||||
}
|
||||
|
||||
// Description adapted from CoreCLR: see https://source.dot.net/#System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs,285.
|
||||
// CLR objects are laid out in memory as follows:
|
||||
// [ sync block || pMethodTable || raw data .. ]
|
||||
// ^ ^
|
||||
// | \-- ref Unsafe.As<RawObjectData>(owner).Data
|
||||
// \-- object
|
||||
// The reference to RawObjectData.Data points to the first data byte in the
|
||||
// target object, skipping over the sync block, method table and string length.
|
||||
// This type is not nested to avoid creating multiple generic types.
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
internal sealed class RawObjectData
|
||||
{
|
||||
#pragma warning disable CS0649 // Unassigned fields
|
||||
#pragma warning disable SA1401 // Fields should be private
|
||||
[FieldOffset(0)]
|
||||
public byte Data;
|
||||
#pragma warning restore CS0649
|
||||
#pragma warning restore SA1401
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,8 +5,6 @@
|
|||
using System.Buffers;
|
||||
using System.IO;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Streams
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
@ -2,15 +2,13 @@
|
|||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
#if NETSTANDARD2_1
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Streams
|
||||
{
|
||||
/// <summary>
|
||||
|
@ -19,7 +17,7 @@ namespace Microsoft.Toolkit.HighPerformance.Streams
|
|||
internal partial class MemoryStream
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override void CopyTo(Stream destination, int bufferSize)
|
||||
public sealed override void CopyTo(Stream destination, int bufferSize)
|
||||
{
|
||||
ValidateDisposed();
|
||||
|
||||
|
@ -31,7 +29,7 @@ namespace Microsoft.Toolkit.HighPerformance.Streams
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
|
||||
public sealed override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
|
@ -55,7 +53,7 @@ namespace Microsoft.Toolkit.HighPerformance.Streams
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
|
||||
public sealed override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
|
@ -79,7 +77,7 @@ namespace Microsoft.Toolkit.HighPerformance.Streams
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int Read(Span<byte> buffer)
|
||||
public sealed override int Read(Span<byte> buffer)
|
||||
{
|
||||
ValidateDisposed();
|
||||
|
||||
|
@ -97,7 +95,7 @@ namespace Microsoft.Toolkit.HighPerformance.Streams
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Write(ReadOnlySpan<byte> buffer)
|
||||
public sealed override void Write(ReadOnlySpan<byte> buffer)
|
||||
{
|
||||
ValidateDisposed();
|
||||
ValidateCanWrite();
|
||||
|
|
|
@ -3,12 +3,9 @@
|
|||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Streams
|
||||
{
|
||||
/// <summary>
|
||||
|
@ -71,19 +68,19 @@ namespace Microsoft.Toolkit.HighPerformance.Streams
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="InvalidOperationException"/> when trying to write too many bytes to the target stream.
|
||||
/// Throws an <see cref="ArgumentException"/> when trying to write too many bytes to the target stream.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void ThrowArgumentExceptionForEndOfStreamOnWrite()
|
||||
{
|
||||
throw new InvalidOperationException("The current stream can't contain the requested input data.");
|
||||
throw new ArgumentException("The current stream can't contain the requested input data.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a <see cref="NotSupportedException"/> when trying to set the length of the stream.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static void ThrowNotSupportedExceptionForSetLength()
|
||||
private static void ThrowNotSupportedExceptionForSetLength()
|
||||
{
|
||||
throw new NotSupportedException("Setting the length is not supported for this stream.");
|
||||
}
|
||||
|
@ -91,9 +88,9 @@ namespace Microsoft.Toolkit.HighPerformance.Streams
|
|||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when using an invalid seek mode.
|
||||
/// </summary>
|
||||
/// <returns>Nothing, as this method throws unconditionally.</returns>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1615", Justification = "Throw method")]
|
||||
public static long ThrowArgumentExceptionForSeekOrigin()
|
||||
private static long ThrowArgumentExceptionForSeekOrigin()
|
||||
{
|
||||
throw new ArgumentException("The input seek mode is not valid.", "origin");
|
||||
}
|
||||
|
@ -102,7 +99,7 @@ namespace Microsoft.Toolkit.HighPerformance.Streams
|
|||
/// Throws an <see cref="ObjectDisposedException"/> when using a disposed <see cref="Stream"/> instance.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static void ThrowObjectDisposedException()
|
||||
private static void ThrowObjectDisposedException()
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(memory), "The current stream has already been disposed");
|
||||
}
|
||||
|
|
|
@ -5,8 +5,6 @@
|
|||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Streams
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
@ -9,8 +9,6 @@ using System.Runtime.InteropServices;
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Streams
|
||||
{
|
||||
/// <summary>
|
||||
|
@ -68,28 +66,28 @@ namespace Microsoft.Toolkit.HighPerformance.Streams
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool CanRead
|
||||
public sealed override bool CanRead
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => !this.disposed;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool CanSeek
|
||||
public sealed override bool CanSeek
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => !this.disposed;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool CanWrite
|
||||
public sealed override bool CanWrite
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => !this.isReadOnly && !this.disposed;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override long Length
|
||||
public sealed override long Length
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
|
@ -101,7 +99,7 @@ namespace Microsoft.Toolkit.HighPerformance.Streams
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override long Position
|
||||
public sealed override long Position
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
|
@ -122,7 +120,7 @@ namespace Microsoft.Toolkit.HighPerformance.Streams
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
|
||||
public sealed override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
|
@ -146,12 +144,12 @@ namespace Microsoft.Toolkit.HighPerformance.Streams
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Flush()
|
||||
public sealed override void Flush()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Task FlushAsync(CancellationToken cancellationToken)
|
||||
public sealed override Task FlushAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
|
@ -162,7 +160,7 @@ namespace Microsoft.Toolkit.HighPerformance.Streams
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Task<int> ReadAsync(byte[]? buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
public sealed override Task<int> ReadAsync(byte[]? buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
|
@ -186,7 +184,7 @@ namespace Microsoft.Toolkit.HighPerformance.Streams
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Task WriteAsync(byte[]? buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
public sealed override Task WriteAsync(byte[]? buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
|
@ -210,7 +208,7 @@ namespace Microsoft.Toolkit.HighPerformance.Streams
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
public sealed override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
ValidateDisposed();
|
||||
|
||||
|
@ -230,13 +228,13 @@ namespace Microsoft.Toolkit.HighPerformance.Streams
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void SetLength(long value)
|
||||
public sealed override void SetLength(long value)
|
||||
{
|
||||
ThrowNotSupportedExceptionForSetLength();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int Read(byte[]? buffer, int offset, int count)
|
||||
public sealed override int Read(byte[]? buffer, int offset, int count)
|
||||
{
|
||||
ValidateDisposed();
|
||||
ValidateBuffer(buffer, offset, count);
|
||||
|
@ -257,7 +255,7 @@ namespace Microsoft.Toolkit.HighPerformance.Streams
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int ReadByte()
|
||||
public sealed override int ReadByte()
|
||||
{
|
||||
ValidateDisposed();
|
||||
|
||||
|
@ -270,7 +268,7 @@ namespace Microsoft.Toolkit.HighPerformance.Streams
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Write(byte[]? buffer, int offset, int count)
|
||||
public sealed override void Write(byte[]? buffer, int offset, int count)
|
||||
{
|
||||
ValidateDisposed();
|
||||
ValidateCanWrite();
|
||||
|
@ -289,7 +287,7 @@ namespace Microsoft.Toolkit.HighPerformance.Streams
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void WriteByte(byte value)
|
||||
public sealed override void WriteByte(byte value)
|
||||
{
|
||||
ValidateDisposed();
|
||||
ValidateCanWrite();
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netcoreapp2.1;netcoreapp3.0</TargetFrameworks>
|
||||
<TargetFrameworks>netcoreapp2.1;netcoreapp3.1</TargetFrameworks>
|
||||
<LangVersion>8.0</LangVersion>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<IsPackable>false</IsPackable>
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
// 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.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Microsoft.Toolkit.HighPerformance.Buffers;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace UnitTests.HighPerformance.Buffers
|
||||
{
|
||||
[TestClass]
|
||||
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649", Justification = "Test class for generic type")]
|
||||
public class Test_MemoryBufferWriterOfT
|
||||
{
|
||||
[TestCategory("MemoryBufferWriterOfT")]
|
||||
[TestMethod]
|
||||
public void Test_MemoryBufferWriterOfT_AllocateAndGetMemoryAndSpan()
|
||||
{
|
||||
Memory<byte> memory = new byte[256];
|
||||
|
||||
var writer = new MemoryBufferWriter<byte>(memory);
|
||||
|
||||
Assert.AreEqual(writer.Capacity, 256);
|
||||
Assert.AreEqual(writer.FreeCapacity, 256);
|
||||
Assert.AreEqual(writer.WrittenCount, 0);
|
||||
Assert.IsTrue(writer.WrittenMemory.IsEmpty);
|
||||
Assert.IsTrue(writer.WrittenSpan.IsEmpty);
|
||||
|
||||
Span<byte> span = writer.GetSpan(43);
|
||||
|
||||
Assert.AreEqual(span.Length, memory.Length);
|
||||
|
||||
writer.Advance(43);
|
||||
|
||||
Assert.AreEqual(writer.Capacity, 256);
|
||||
Assert.AreEqual(writer.FreeCapacity, 256 - 43);
|
||||
Assert.AreEqual(writer.WrittenCount, 43);
|
||||
Assert.AreEqual(writer.WrittenMemory.Length, 43);
|
||||
Assert.AreEqual(writer.WrittenSpan.Length, 43);
|
||||
|
||||
Assert.AreEqual(memory.Length - 43, writer.GetSpan().Length);
|
||||
Assert.AreEqual(memory.Length - 43, writer.GetMemory().Length);
|
||||
Assert.AreEqual(memory.Length - 43, writer.GetSpan(22).Length);
|
||||
Assert.AreEqual(memory.Length - 43, writer.GetMemory(22).Length);
|
||||
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => writer.Advance(-1));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => writer.GetMemory(-1));
|
||||
Assert.ThrowsException<ArgumentException>(() => writer.GetSpan(1024));
|
||||
Assert.ThrowsException<ArgumentException>(() => writer.GetMemory(1024));
|
||||
Assert.ThrowsException<ArgumentException>(() => writer.Advance(1024));
|
||||
}
|
||||
|
||||
[TestCategory("MemoryBufferWriterOfT")]
|
||||
[TestMethod]
|
||||
public void Test_MemoryBufferWriterOfT_Clear()
|
||||
{
|
||||
Memory<byte> memory = new byte[256];
|
||||
|
||||
var writer = new MemoryBufferWriter<byte>(memory);
|
||||
|
||||
Span<byte> span = writer.GetSpan(4).Slice(0, 4);
|
||||
|
||||
byte[] data = { 1, 2, 3, 4 };
|
||||
|
||||
data.CopyTo(span);
|
||||
|
||||
writer.Advance(4);
|
||||
|
||||
Assert.AreEqual(writer.WrittenCount, 4);
|
||||
Assert.IsTrue(span.SequenceEqual(data));
|
||||
|
||||
writer.Clear();
|
||||
|
||||
Assert.AreEqual(writer.WrittenCount, 0);
|
||||
Assert.IsTrue(span.ToArray().All(b => b == 0));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -64,6 +64,10 @@ namespace UnitTests.HighPerformance.Buffers
|
|||
buffer.Dispose();
|
||||
buffer.Dispose();
|
||||
buffer.Dispose();
|
||||
|
||||
// This test consists in just getting here without crashes.
|
||||
// We're validating that calling Dispose multiple times
|
||||
// by accident doesn't cause issues, and just does nothing.
|
||||
}
|
||||
|
||||
[TestCategory("HashCodeOfT")]
|
||||
|
|
|
@ -277,7 +277,7 @@ namespace UnitTests.HighPerformance.Extensions
|
|||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => array.GetColumn(0).ToArray());
|
||||
}
|
||||
|
||||
#if NETCOREAPP3_0
|
||||
#if NETCOREAPP3_1
|
||||
[TestCategory("ArrayExtensions")]
|
||||
[TestMethod]
|
||||
public void Test_ArrayExtensions_2D_AsSpan_Empty()
|
||||
|
|
|
@ -26,5 +26,23 @@ namespace UnitTests.HighPerformance.Extensions
|
|||
Assert.AreEqual(0, false.ToInt(), nameof(Test_BoolExtensions_False));
|
||||
Assert.AreEqual(0, (DateTime.Now.Year > 3000).ToInt(), nameof(Test_BoolExtensions_False));
|
||||
}
|
||||
|
||||
[TestCategory("BoolExtensions")]
|
||||
[TestMethod]
|
||||
[DataRow(true, -1)]
|
||||
[DataRow(false, 0)]
|
||||
public void Test_BoolExtensions_ToBitwiseMask32(bool value, int result)
|
||||
{
|
||||
Assert.AreEqual(value.ToBitwiseMask32(), result);
|
||||
}
|
||||
|
||||
[TestCategory("BoolExtensions")]
|
||||
[TestMethod]
|
||||
[DataRow(true, -1)]
|
||||
[DataRow(false, 0)]
|
||||
public void Test_BoolExtensions_ToBitwiseMask64(bool value, long result)
|
||||
{
|
||||
Assert.AreEqual(value.ToBitwiseMask64(), result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,129 @@
|
|||
// 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;
|
||||
#if !WINDOWS_UWP
|
||||
using System.Buffers;
|
||||
#endif
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.Toolkit.HighPerformance.Buffers;
|
||||
using Microsoft.Toolkit.HighPerformance.Extensions;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace UnitTests.HighPerformance.Extensions
|
||||
{
|
||||
[TestClass]
|
||||
public class Test_IBufferWriterExtensions
|
||||
{
|
||||
[TestCategory("IBufferWriterExtensions")]
|
||||
[TestMethod]
|
||||
public void Test_IBufferWriterExtensions_WriteReadOverBytes()
|
||||
{
|
||||
ArrayPoolBufferWriter<byte> writer = new ArrayPoolBufferWriter<byte>();
|
||||
|
||||
byte b = 255;
|
||||
char c = '$';
|
||||
float f = 3.14f;
|
||||
double d = 6.28;
|
||||
Guid guid = Guid.NewGuid();
|
||||
|
||||
writer.Write(b);
|
||||
writer.Write(c);
|
||||
writer.Write(f);
|
||||
writer.Write(d);
|
||||
writer.Write(guid);
|
||||
|
||||
int count = sizeof(byte) + sizeof(char) + sizeof(float) + sizeof(double) + Unsafe.SizeOf<Guid>();
|
||||
|
||||
Assert.AreEqual(count, writer.WrittenCount);
|
||||
|
||||
using Stream reader = writer.WrittenMemory.AsStream();
|
||||
|
||||
Assert.AreEqual(b, reader.Read<byte>());
|
||||
Assert.AreEqual(c, reader.Read<char>());
|
||||
Assert.AreEqual(f, reader.Read<float>());
|
||||
Assert.AreEqual(d, reader.Read<double>());
|
||||
Assert.AreEqual(guid, reader.Read<Guid>());
|
||||
}
|
||||
|
||||
[TestCategory("IBufferWriterExtensions")]
|
||||
[TestMethod]
|
||||
public void Test_IBufferWriterExtensions_WriteReadItem_Guid()
|
||||
{
|
||||
Test_IBufferWriterExtensions_WriteReadItem(Guid.NewGuid(), Guid.NewGuid());
|
||||
}
|
||||
|
||||
[TestCategory("IBufferWriterExtensions")]
|
||||
[TestMethod]
|
||||
public void Test_IBufferWriterExtensions_WriteReadItem_String()
|
||||
{
|
||||
Test_IBufferWriterExtensions_WriteReadItem("Hello", "World");
|
||||
}
|
||||
|
||||
private static void Test_IBufferWriterExtensions_WriteReadItem<T>(T a, T b)
|
||||
where T : IEquatable<T>
|
||||
{
|
||||
ArrayPoolBufferWriter<T> writer = new ArrayPoolBufferWriter<T>();
|
||||
|
||||
writer.Write(a);
|
||||
writer.Write(b);
|
||||
|
||||
Assert.AreEqual(2, writer.WrittenCount);
|
||||
|
||||
ReadOnlySpan<T> span = writer.WrittenSpan;
|
||||
|
||||
Assert.AreEqual(a, span[0]);
|
||||
Assert.AreEqual(b, span[1]);
|
||||
}
|
||||
|
||||
[TestCategory("IBufferWriterExtensions")]
|
||||
[TestMethod]
|
||||
public void Test_IBufferWriterExtensions_WriteReadOverBytes_ReadOnlySpan()
|
||||
{
|
||||
int[] buffer = new int[128];
|
||||
|
||||
var random = new Random(42);
|
||||
|
||||
foreach (ref var n in buffer.AsSpan())
|
||||
{
|
||||
n = random.Next(int.MinValue, int.MaxValue);
|
||||
}
|
||||
|
||||
ArrayPoolBufferWriter<byte> writer = new ArrayPoolBufferWriter<byte>();
|
||||
|
||||
writer.Write<int>(buffer);
|
||||
|
||||
Assert.AreEqual(sizeof(int) * buffer.Length, writer.WrittenCount);
|
||||
|
||||
ReadOnlySpan<byte> span = writer.WrittenSpan;
|
||||
|
||||
Assert.IsTrue(span.SequenceEqual(buffer.AsSpan().AsBytes()));
|
||||
}
|
||||
|
||||
[TestCategory("IBufferWriterExtensions")]
|
||||
[TestMethod]
|
||||
public void Test_IBufferWriterExtensions_WriteReadOverItems_ReadOnlySpan()
|
||||
{
|
||||
int[] buffer = new int[128];
|
||||
|
||||
var random = new Random(42);
|
||||
|
||||
foreach (ref var n in buffer.AsSpan())
|
||||
{
|
||||
n = random.Next(int.MinValue, int.MaxValue);
|
||||
}
|
||||
|
||||
ArrayPoolBufferWriter<int> writer = new ArrayPoolBufferWriter<int>();
|
||||
|
||||
writer.Write(buffer.AsSpan());
|
||||
|
||||
Assert.AreEqual(buffer.Length, writer.WrittenCount);
|
||||
|
||||
ReadOnlySpan<int> span = writer.WrittenSpan;
|
||||
|
||||
Assert.IsTrue(span.SequenceEqual(buffer.AsSpan()));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -21,7 +21,8 @@ namespace UnitTests.HighPerformance.Extensions
|
|||
Stream stream = buffer.AsStream();
|
||||
|
||||
Assert.IsNotNull(stream);
|
||||
Assert.AreEqual(stream.Length, buffer.Length);
|
||||
Assert.AreEqual(buffer.Length, 0);
|
||||
Assert.AreEqual(stream.Length, 0);
|
||||
Assert.IsTrue(stream.CanWrite);
|
||||
}
|
||||
|
||||
|
@ -37,5 +38,30 @@ namespace UnitTests.HighPerformance.Extensions
|
|||
Assert.AreEqual(stream.Length, buffer.Length);
|
||||
Assert.IsTrue(stream.CanWrite);
|
||||
}
|
||||
|
||||
[TestCategory("IMemoryOwnerExtensions")]
|
||||
[TestMethod]
|
||||
public void Test_MemoryExtensions_IMemoryOwnerStream_DoesNotAlterExistingData()
|
||||
{
|
||||
MemoryOwner<byte> buffer = MemoryOwner<byte>.Allocate(1024);
|
||||
|
||||
// Fill the buffer with sample data
|
||||
for (int i = 0; i < buffer.Length; i++)
|
||||
{
|
||||
buffer.Span[i] = unchecked((byte)(i & byte.MaxValue));
|
||||
}
|
||||
|
||||
Stream stream = buffer.AsStream();
|
||||
|
||||
Assert.IsNotNull(stream);
|
||||
Assert.AreEqual(stream.Length, buffer.Length);
|
||||
Assert.IsTrue(stream.CanWrite);
|
||||
|
||||
// Validate that creating the stream doesn't alter the underlying buffer
|
||||
for (int i = 0; i < buffer.Length; i++)
|
||||
{
|
||||
Assert.AreEqual(buffer.Span[i], unchecked((byte)(i & byte.MaxValue)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.Toolkit.HighPerformance.Extensions;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
|
@ -11,6 +12,40 @@ namespace UnitTests.HighPerformance.Extensions
|
|||
[TestClass]
|
||||
public class Test_ObjectExtensions
|
||||
{
|
||||
[TestCategory("ObjectExtensions")]
|
||||
[TestMethod]
|
||||
public void Test_DangerousGetObjectDataByteOffset()
|
||||
{
|
||||
var a = new TestClass { Number = 42, Character = 'a', Text = "Hello" };
|
||||
|
||||
IntPtr ptr = a.DangerousGetObjectDataByteOffset(ref a.Number);
|
||||
|
||||
ref int number = ref a.DangerousGetObjectDataReferenceAt<int>(ptr);
|
||||
|
||||
Assert.IsTrue(Unsafe.AreSame(ref a.Number, ref number));
|
||||
|
||||
ptr = a.DangerousGetObjectDataByteOffset(ref a.Character);
|
||||
|
||||
ref char character = ref a.DangerousGetObjectDataReferenceAt<char>(ptr);
|
||||
|
||||
Assert.IsTrue(Unsafe.AreSame(ref a.Character, ref character));
|
||||
|
||||
ptr = a.DangerousGetObjectDataByteOffset(ref a.Text);
|
||||
|
||||
ref string text = ref a.DangerousGetObjectDataReferenceAt<string>(ptr);
|
||||
|
||||
Assert.IsTrue(Unsafe.AreSame(ref a.Text, ref text));
|
||||
}
|
||||
|
||||
internal class TestClass
|
||||
{
|
||||
#pragma warning disable SA1401 // Fields should be private
|
||||
public int Number;
|
||||
public char Character;
|
||||
public string Text;
|
||||
#pragma warning restore SA1401
|
||||
}
|
||||
|
||||
[TestCategory("ObjectExtensions")]
|
||||
[TestMethod]
|
||||
public void Test_BoxOfT_PrimitiveTypes()
|
||||
|
|
|
@ -43,6 +43,7 @@ namespace UnitTests.HighPerformance.Extensions
|
|||
{
|
||||
TestForType((int)37438941, CreateRandomData);
|
||||
TestForType((uint)37438941, CreateRandomData);
|
||||
TestForType(MathF.PI, CreateRandomData);
|
||||
}
|
||||
|
||||
[TestCategory("ReadOnlySpanExtensions")]
|
||||
|
@ -52,6 +53,7 @@ namespace UnitTests.HighPerformance.Extensions
|
|||
{
|
||||
TestForType((long)47128480128401, CreateRandomData);
|
||||
TestForType((ulong)47128480128401, CreateRandomData);
|
||||
TestForType(Math.PI, CreateRandomData);
|
||||
}
|
||||
|
||||
[TestCategory("ReadOnlySpanExtensions")]
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Toolkit.HighPerformance.Extensions;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
|
@ -52,24 +52,159 @@ namespace UnitTests.HighPerformance.Extensions
|
|||
|
||||
[TestCategory("ReadOnlySpanExtensions")]
|
||||
[TestMethod]
|
||||
[SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1009", Justification = "List<T> of value tuple")]
|
||||
[DataRow(0)]
|
||||
[DataRow(4)]
|
||||
[DataRow(22)]
|
||||
[DataRow(43)]
|
||||
[DataRow(44)]
|
||||
[DataRow(45)]
|
||||
[DataRow(46)]
|
||||
[DataRow(100)]
|
||||
[DataRow(int.MaxValue)]
|
||||
[DataRow(-1)]
|
||||
[DataRow(int.MinValue)]
|
||||
public void Test_ReadOnlySpanExtensions_DangerousGetLookupReferenceAt(int i)
|
||||
{
|
||||
ReadOnlySpan<byte> table = new byte[]
|
||||
{
|
||||
0xFF, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 1, 1, 0, 1, 1, 1, 1,
|
||||
0, 1, 0, 1, 0, 1, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 1, 0, 1
|
||||
};
|
||||
|
||||
ref byte ri = ref Unsafe.AsRef(table.DangerousGetLookupReferenceAt(i));
|
||||
|
||||
bool isInRange = (uint)i < (uint)table.Length;
|
||||
|
||||
if (isInRange)
|
||||
{
|
||||
Assert.IsTrue(Unsafe.AreSame(ref ri, ref Unsafe.AsRef(table[i])));
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.IsTrue(Unsafe.AreSame(ref ri, ref MemoryMarshal.GetReference(table)));
|
||||
}
|
||||
}
|
||||
|
||||
[TestCategory("ReadOnlySpanExtensions")]
|
||||
[TestMethod]
|
||||
public void Test_ReadOnlySpanExtensions_IndexOf_Empty()
|
||||
{
|
||||
static void Test<T>()
|
||||
{
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() =>
|
||||
{
|
||||
T a = default;
|
||||
|
||||
default(ReadOnlySpan<T>).IndexOf(in a);
|
||||
});
|
||||
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() =>
|
||||
{
|
||||
ReadOnlySpan<T> data = new T[] { default };
|
||||
|
||||
data.Slice(1).IndexOf(in data[0]);
|
||||
});
|
||||
}
|
||||
|
||||
Test<byte>();
|
||||
Test<int>();
|
||||
Test<Guid>();
|
||||
Test<string>();
|
||||
Test<object>();
|
||||
Test<IEnumerable<int>>();
|
||||
}
|
||||
|
||||
[TestCategory("ReadOnlySpanExtensions")]
|
||||
[TestMethod]
|
||||
public void Test_ReadOnlySpanExtensions_IndexOf_NotEmpty()
|
||||
{
|
||||
static void Test<T>()
|
||||
{
|
||||
ReadOnlySpan<T> data = new T[] { default, default, default, default };
|
||||
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
Assert.AreEqual(i, data.IndexOf(in data[i]));
|
||||
}
|
||||
}
|
||||
|
||||
Test<byte>();
|
||||
Test<int>();
|
||||
Test<Guid>();
|
||||
Test<string>();
|
||||
Test<object>();
|
||||
Test<IEnumerable<int>>();
|
||||
}
|
||||
|
||||
[TestCategory("ReadOnlySpanExtensions")]
|
||||
[TestMethod]
|
||||
public void Test_ReadOnlySpanExtensions_IndexOf_NotEmpty_OutOfRange()
|
||||
{
|
||||
static void Test<T>()
|
||||
{
|
||||
// Before start
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() =>
|
||||
{
|
||||
ReadOnlySpan<T> data = new T[] { default, default, default, default };
|
||||
|
||||
data.Slice(1).IndexOf(in data[0]);
|
||||
});
|
||||
|
||||
// After end
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() =>
|
||||
{
|
||||
ReadOnlySpan<T> data = new T[] { default, default, default, default };
|
||||
|
||||
data.Slice(0, 2).IndexOf(in data[2]);
|
||||
});
|
||||
|
||||
// Local variable
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() =>
|
||||
{
|
||||
var dummy = new T[] { default };
|
||||
ReadOnlySpan<T> data = new T[] { default, default, default, default };
|
||||
|
||||
data.IndexOf(in dummy[0]);
|
||||
});
|
||||
}
|
||||
|
||||
Test<byte>();
|
||||
Test<int>();
|
||||
Test<Guid>();
|
||||
Test<string>();
|
||||
Test<object>();
|
||||
Test<IEnumerable<int>>();
|
||||
}
|
||||
|
||||
[TestCategory("ReadOnlySpanExtensions")]
|
||||
[TestMethod]
|
||||
public void Test_ReadOnlySpanExtensions_Enumerate()
|
||||
{
|
||||
ReadOnlySpan<int> data = CreateRandomData<int>(12, default).AsSpan();
|
||||
ReadOnlySpan<int> data = new[] { 1, 2, 3, 4, 5, 6, 7 };
|
||||
|
||||
List<(int Index, int Value)> values = new List<(int, int)>();
|
||||
int i = 0;
|
||||
|
||||
foreach (var item in data.Enumerate())
|
||||
{
|
||||
values.Add(item);
|
||||
Assert.IsTrue(Unsafe.AreSame(ref Unsafe.AsRef(data[i]), ref Unsafe.AsRef(item.Value)));
|
||||
Assert.AreEqual(i, item.Index);
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
Assert.AreEqual(values.Count, data.Length);
|
||||
[TestCategory("ReadOnlySpanExtensions")]
|
||||
[TestMethod]
|
||||
public void Test_ReadOnlySpanExtensions_Enumerate_Empty()
|
||||
{
|
||||
ReadOnlySpan<int> data = Array.Empty<int>();
|
||||
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
foreach (var item in data.Enumerate())
|
||||
{
|
||||
Assert.AreEqual(data[i], values[i].Value);
|
||||
Assert.AreEqual(i, values[i].Index);
|
||||
Assert.Fail("Empty source sequence");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -93,7 +228,7 @@ namespace UnitTests.HighPerformance.Extensions
|
|||
|
||||
[TestCategory("ReadOnlySpanExtensions")]
|
||||
[TestMethod]
|
||||
public void Test_ReadOnlySpanExtensions_Tokenize_Csv()
|
||||
public void Test_ReadOnlySpanExtensions_Tokenize_Comma()
|
||||
{
|
||||
string text = "name,surname,city,year,profession,age";
|
||||
|
||||
|
@ -111,7 +246,7 @@ namespace UnitTests.HighPerformance.Extensions
|
|||
|
||||
[TestCategory("ReadOnlySpanExtensions")]
|
||||
[TestMethod]
|
||||
public void Test_ReadOnlySpanExtensions_Tokenize_CsvWithMissingValues()
|
||||
public void Test_ReadOnlySpanExtensions_Tokenize_CommaWithMissingValues()
|
||||
{
|
||||
string text = ",name,,city,,,profession,,age,,";
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.Toolkit.HighPerformance.Extensions;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
@ -52,27 +51,121 @@ namespace UnitTests.HighPerformance.Extensions
|
|||
|
||||
[TestCategory("SpanExtensions")]
|
||||
[TestMethod]
|
||||
[SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1009", Justification = "List<T> of value tuple")]
|
||||
public void Test_SpanExtensions_IndexOf_Empty()
|
||||
{
|
||||
static void Test<T>()
|
||||
{
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() =>
|
||||
{
|
||||
T a = default;
|
||||
|
||||
default(Span<T>).IndexOf(ref a);
|
||||
});
|
||||
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() =>
|
||||
{
|
||||
Span<T> data = new T[] { default };
|
||||
|
||||
data.Slice(1).IndexOf(ref data[0]);
|
||||
});
|
||||
}
|
||||
|
||||
Test<byte>();
|
||||
Test<int>();
|
||||
Test<Guid>();
|
||||
Test<string>();
|
||||
Test<object>();
|
||||
Test<IEnumerable<int>>();
|
||||
}
|
||||
|
||||
[TestCategory("SpanExtensions")]
|
||||
[TestMethod]
|
||||
public void Test_SpanExtensions_IndexOf_NotEmpty()
|
||||
{
|
||||
static void Test<T>()
|
||||
{
|
||||
Span<T> data = new T[] { default, default, default, default };
|
||||
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
Assert.AreEqual(i, data.IndexOf(ref data[i]));
|
||||
}
|
||||
}
|
||||
|
||||
Test<byte>();
|
||||
Test<int>();
|
||||
Test<Guid>();
|
||||
Test<string>();
|
||||
Test<object>();
|
||||
Test<IEnumerable<int>>();
|
||||
}
|
||||
|
||||
[TestCategory("SpanExtensions")]
|
||||
[TestMethod]
|
||||
public void Test_SpanExtensions_IndexOf_NotEmpty_OutOfRange()
|
||||
{
|
||||
static void Test<T>()
|
||||
{
|
||||
// Before start
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() =>
|
||||
{
|
||||
Span<T> data = new T[] { default, default, default, default };
|
||||
|
||||
data.Slice(1).IndexOf(ref data[0]);
|
||||
});
|
||||
|
||||
// After end
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() =>
|
||||
{
|
||||
Span<T> data = new T[] { default, default, default, default };
|
||||
|
||||
data.Slice(0, 2).IndexOf(ref data[2]);
|
||||
});
|
||||
|
||||
// Local variable
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() =>
|
||||
{
|
||||
var dummy = new T[] { default };
|
||||
Span<T> data = new T[] { default, default, default, default };
|
||||
|
||||
data.IndexOf(ref dummy[0]);
|
||||
});
|
||||
}
|
||||
|
||||
Test<byte>();
|
||||
Test<int>();
|
||||
Test<Guid>();
|
||||
Test<string>();
|
||||
Test<object>();
|
||||
Test<IEnumerable<int>>();
|
||||
}
|
||||
|
||||
[TestCategory("SpanExtensions")]
|
||||
[TestMethod]
|
||||
public void Test_SpanExtensions_Enumerate()
|
||||
{
|
||||
Span<int> data = new[] { 1, 2, 3, 4, 5, 6, 7 };
|
||||
|
||||
List<(int Index, int Value)> values = new List<(int, int)>();
|
||||
int i = 0;
|
||||
|
||||
foreach (var item in data.Enumerate())
|
||||
{
|
||||
values.Add((item.Index, item.Value));
|
||||
Assert.IsTrue(Unsafe.AreSame(ref data[i], ref item.Value));
|
||||
Assert.AreEqual(i, item.Index);
|
||||
|
||||
item.Value = item.Index * 10;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
Assert.AreEqual(values.Count, data.Length);
|
||||
[TestCategory("SpanExtensions")]
|
||||
[TestMethod]
|
||||
public void Test_SpanExtensions_Enumerate_Empty()
|
||||
{
|
||||
Span<int> data = Array.Empty<int>();
|
||||
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
foreach (var item in data.Enumerate())
|
||||
{
|
||||
Assert.AreEqual(data[i], i * 10);
|
||||
Assert.AreEqual(i, values[i].Index);
|
||||
Assert.AreEqual(i + 1, values[i].Value);
|
||||
Assert.Fail("Empty source sequence");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,7 +48,7 @@ namespace UnitTests.HighPerformance.Extensions
|
|||
{
|
||||
for (int j = 0; j < 10; j++)
|
||||
{
|
||||
#if NETCOREAPP2_1 || WINDOWS_UWP
|
||||
#if WINDOWS_UWP
|
||||
using (SpinLockExtensions.Enter(spinLockOwner, ref spinLockOwner.Lock))
|
||||
#else
|
||||
using (spinLockOwner.Lock.Enter())
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
// 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.IO;
|
||||
using Microsoft.Toolkit.HighPerformance.Extensions;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace UnitTests.HighPerformance.Extensions
|
||||
{
|
||||
[TestClass]
|
||||
public class Test_StreamExtensions
|
||||
{
|
||||
[TestCategory("StreamExtensions")]
|
||||
[TestMethod]
|
||||
public void Test_StreamExtensions_ReadWrite()
|
||||
{
|
||||
// bool (1), int (4), float (4), long (8) = 17 bytes.
|
||||
// Leave two extra bytes for the partial read (fail).
|
||||
Stream stream = new byte[19].AsMemory().AsStream();
|
||||
|
||||
stream.Write(true);
|
||||
stream.Write(42);
|
||||
stream.Write(3.14f);
|
||||
stream.Write(unchecked(uint.MaxValue * 324823489204ul));
|
||||
|
||||
Assert.AreEqual(stream.Position, 17);
|
||||
|
||||
Assert.ThrowsException<ArgumentException>(() => stream.Write(long.MaxValue));
|
||||
|
||||
stream.Position = 0;
|
||||
|
||||
Assert.AreEqual(true, stream.Read<bool>());
|
||||
Assert.AreEqual(42, stream.Read<int>());
|
||||
Assert.AreEqual(3.14f, stream.Read<float>());
|
||||
Assert.AreEqual(unchecked(uint.MaxValue * 324823489204ul), stream.Read<ulong>());
|
||||
|
||||
Assert.ThrowsException<InvalidOperationException>(() => stream.Read<long>());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,7 +5,7 @@
|
|||
using Microsoft.Toolkit.HighPerformance.Helpers;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace UnitTests.HighPerformance.Extensions
|
||||
namespace UnitTests.HighPerformance.Helpers
|
||||
{
|
||||
[TestClass]
|
||||
public class Test_BitHelper
|
||||
|
@ -25,6 +25,58 @@ namespace UnitTests.HighPerformance.Extensions
|
|||
Assert.IsTrue(BitHelper.HasFlag(value, 31));
|
||||
}
|
||||
|
||||
[TestCategory("BitHelper")]
|
||||
[TestMethod]
|
||||
[DataRow(0, true)]
|
||||
[DataRow(1, false)]
|
||||
[DataRow(6, true)]
|
||||
[DataRow(7, false)]
|
||||
[DataRow(12, true)]
|
||||
[DataRow(14, true)]
|
||||
[DataRow(15, false)]
|
||||
[DataRow(20, false)]
|
||||
[DataRow(21, true)]
|
||||
[DataRow(30, false)]
|
||||
[DataRow(31, true)]
|
||||
[DataRow(42, false)]
|
||||
[DataRow(43741384, false)]
|
||||
[DataRow(int.MaxValue, false)]
|
||||
public void Test_BitHelper_HasLookupFlag_Default(int index, bool flag)
|
||||
{
|
||||
const uint value = 0xAAAA5555u;
|
||||
|
||||
Assert.AreEqual(flag, BitHelper.HasLookupFlag(value, index));
|
||||
}
|
||||
|
||||
[TestCategory("BitHelper")]
|
||||
[TestMethod]
|
||||
[DataRow('+', true)]
|
||||
[DataRow('-', true)]
|
||||
[DataRow('>', true)]
|
||||
[DataRow('<', true)]
|
||||
[DataRow('.', true)]
|
||||
[DataRow(',', true)]
|
||||
[DataRow('(', true)]
|
||||
[DataRow(')', true)]
|
||||
[DataRow(':', true)]
|
||||
[DataRow('a', false)]
|
||||
[DataRow('%', false)]
|
||||
[DataRow('A', false)]
|
||||
[DataRow('€', false)]
|
||||
[DataRow(0, false)]
|
||||
[DataRow(1, false)]
|
||||
[DataRow(-1, false)]
|
||||
[DataRow(int.MaxValue, false)]
|
||||
[DataRow(int.MinValue, false)]
|
||||
[DataRow(short.MaxValue, false)]
|
||||
[DataRow(short.MinValue, false)]
|
||||
public void Test_BitHelper_HasLookupFlag32_WithMin(int x, bool flag)
|
||||
{
|
||||
const uint mask = 8126587;
|
||||
|
||||
Assert.AreEqual(flag, BitHelper.HasLookupFlag(mask, x, 40));
|
||||
}
|
||||
|
||||
[TestCategory("BitHelper")]
|
||||
[TestMethod]
|
||||
public void Test_BitHelper_SetFlag_UInt32()
|
||||
|
@ -35,6 +87,46 @@ namespace UnitTests.HighPerformance.Extensions
|
|||
Assert.AreEqual(unchecked((uint)int.MinValue), BitHelper.SetFlag(0u, 31, true));
|
||||
}
|
||||
|
||||
[TestCategory("BitHelper")]
|
||||
[TestMethod]
|
||||
[DataRow(0u, (byte)0, (byte)0, 0u)]
|
||||
[DataRow(uint.MaxValue, (byte)0, (byte)8, 255u)]
|
||||
[DataRow(uint.MaxValue, (byte)24, (byte)8, 255u)]
|
||||
[DataRow(12345u << 7, (byte)7, (byte)16, 12345u)]
|
||||
[DataRow(3u << 1, (byte)1, (byte)2, 3u)]
|
||||
[DataRow(21u << 17, (byte)17, (byte)5, 21u)]
|
||||
[DataRow(1u << 31, (byte)31, (byte)1, 1u)]
|
||||
public void Test_BitHelper_ExtractRange_UInt32(uint value, byte start, byte length, uint result)
|
||||
{
|
||||
Assert.AreEqual(result, BitHelper.ExtractRange(value, start, length));
|
||||
}
|
||||
|
||||
[TestCategory("BitHelper")]
|
||||
[TestMethod]
|
||||
[DataRow((byte)0, (byte)0, 0u)]
|
||||
[DataRow((byte)0, (byte)8, 255u)]
|
||||
[DataRow((byte)24, (byte)8, 255u)]
|
||||
[DataRow((byte)0, (byte)31, (uint)int.MaxValue)]
|
||||
[DataRow((byte)29, (byte)3, 5u)]
|
||||
[DataRow((byte)7, (byte)14, 12345u)]
|
||||
[DataRow((byte)1, (byte)2, 3u)]
|
||||
[DataRow((byte)17, (byte)5, 21u)]
|
||||
[DataRow((byte)31, (byte)1, 1u)]
|
||||
public void Test_BitHelper_SetRange_UInt32(byte start, byte length, uint flags)
|
||||
{
|
||||
// Specific initial bit mask to check for unwanted modifications
|
||||
const uint value = 0xAAAA5555u;
|
||||
|
||||
uint
|
||||
backup = BitHelper.ExtractRange(value, start, length),
|
||||
result = BitHelper.SetRange(value, start, length, flags),
|
||||
extracted = BitHelper.ExtractRange(result, start, length),
|
||||
restored = BitHelper.SetRange(result, start, length, backup);
|
||||
|
||||
Assert.AreEqual(extracted, flags);
|
||||
Assert.AreEqual(restored, value);
|
||||
}
|
||||
|
||||
[TestCategory("BitHelper")]
|
||||
[TestMethod]
|
||||
public void Test_UInt64Extensions_HasFlag()
|
||||
|
@ -53,6 +145,37 @@ namespace UnitTests.HighPerformance.Extensions
|
|||
Assert.IsTrue(BitHelper.HasFlag(value, 63));
|
||||
}
|
||||
|
||||
[TestCategory("BitHelper")]
|
||||
[TestMethod]
|
||||
[DataRow('+', true)]
|
||||
[DataRow('-', true)]
|
||||
[DataRow('>', true)]
|
||||
[DataRow('<', true)]
|
||||
[DataRow('.', true)]
|
||||
[DataRow(',', true)]
|
||||
[DataRow('[', true)]
|
||||
[DataRow(']', true)]
|
||||
[DataRow('(', true)]
|
||||
[DataRow(')', true)]
|
||||
[DataRow(':', true)]
|
||||
[DataRow('a', false)]
|
||||
[DataRow('%', false)]
|
||||
[DataRow('A', false)]
|
||||
[DataRow('€', false)]
|
||||
[DataRow(0, false)]
|
||||
[DataRow(1, false)]
|
||||
[DataRow(-1, false)]
|
||||
[DataRow(int.MaxValue, false)]
|
||||
[DataRow(int.MinValue, false)]
|
||||
[DataRow(short.MaxValue, false)]
|
||||
[DataRow(short.MinValue, false)]
|
||||
public void Test_BitHelper_HasLookupFlag64_WithMin(int x, bool flag)
|
||||
{
|
||||
const ulong mask = 11258999073931387;
|
||||
|
||||
Assert.AreEqual(flag, BitHelper.HasLookupFlag(mask, x, 40));
|
||||
}
|
||||
|
||||
[TestCategory("BitHelper")]
|
||||
[TestMethod]
|
||||
public void Test_UInt64Extensions_SetFlag()
|
||||
|
@ -63,5 +186,59 @@ namespace UnitTests.HighPerformance.Extensions
|
|||
Assert.AreEqual(1ul << 31, BitHelper.SetFlag(0ul, 31, true));
|
||||
Assert.AreEqual(1ul << 63, BitHelper.SetFlag(0ul, 63, true));
|
||||
}
|
||||
|
||||
[TestCategory("BitHelper")]
|
||||
[TestMethod]
|
||||
[DataRow(0u, (byte)0, (byte)0, 0u)]
|
||||
[DataRow(uint.MaxValue, (byte)0, (byte)8, 255u)]
|
||||
[DataRow(uint.MaxValue, (byte)24, (byte)8, 255u)]
|
||||
[DataRow(12345u << 7, (byte)7, (byte)16, 12345u)]
|
||||
[DataRow(3u << 1, (byte)1, (byte)2, 3u)]
|
||||
[DataRow(21u << 17, (byte)17, (byte)5, 21u)]
|
||||
[DataRow(1u << 31, (byte)31, (byte)1, 1u)]
|
||||
[DataRow(ulong.MaxValue, (byte)0, (byte)8, 255u)]
|
||||
[DataRow(ulong.MaxValue, (byte)24, (byte)8, 255u)]
|
||||
[DataRow(ulong.MaxValue, (byte)44, (byte)8, 255u)]
|
||||
[DataRow(12345ul << 35, (byte)35, (byte)16, 12345ul)]
|
||||
[DataRow(3ul << 56, (byte)56, (byte)2, 3ul)]
|
||||
[DataRow(21ul << 37, (byte)37, (byte)5, 21ul)]
|
||||
[DataRow(1ul << 63, (byte)63, (byte)1, 1ul)]
|
||||
public void Test_BitHelper_ExtractRange_UInt64(ulong value, byte start, byte length, ulong result)
|
||||
{
|
||||
Assert.AreEqual(result, BitHelper.ExtractRange(value, start, length));
|
||||
}
|
||||
|
||||
[TestCategory("BitHelper")]
|
||||
[TestMethod]
|
||||
[DataRow((byte)0, (byte)0, 0u)]
|
||||
[DataRow((byte)0, (byte)8, 255ul)]
|
||||
[DataRow((byte)24, (byte)8, 255ul)]
|
||||
[DataRow((byte)0, (byte)31, (ulong)int.MaxValue)]
|
||||
[DataRow((byte)29, (byte)3, 5ul)]
|
||||
[DataRow((byte)7, (byte)14, 12345ul)]
|
||||
[DataRow((byte)1, (byte)2, 3ul)]
|
||||
[DataRow((byte)17, (byte)5, 21ul)]
|
||||
[DataRow((byte)31, (byte)1, 1ul)]
|
||||
[DataRow((byte)44, (byte)8, 255ul)]
|
||||
[DataRow((byte)23, (byte)8, 255ul)]
|
||||
[DataRow((byte)55, (byte)3, 5ul)]
|
||||
[DataRow((byte)34, (byte)14, 12345ul)]
|
||||
[DataRow((byte)59, (byte)2, 3ul)]
|
||||
[DataRow((byte)45, (byte)5, 21ul)]
|
||||
[DataRow((byte)62, (byte)1, 1ul)]
|
||||
public void Test_BitHelper_SetRange_UInt64(byte start, byte length, ulong flags)
|
||||
{
|
||||
// Specific initial bit mask to check for unwanted modifications
|
||||
const ulong value = 0xAAAA5555AAAA5555u;
|
||||
|
||||
ulong
|
||||
backup = BitHelper.ExtractRange(value, start, length),
|
||||
result = BitHelper.SetRange(value, start, length, flags),
|
||||
extracted = BitHelper.ExtractRange(result, start, length),
|
||||
restored = BitHelper.SetRange(result, start, length, backup);
|
||||
|
||||
Assert.AreEqual(extracted, flags);
|
||||
Assert.AreEqual(restored, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -62,7 +62,7 @@ namespace UnitTests.HighPerformance.Helpers
|
|||
TestForType<char>();
|
||||
}
|
||||
|
||||
#if NETCOREAPP3_0
|
||||
#if NETCOREAPP3_1
|
||||
[TestCategory("HashCodeOfT")]
|
||||
[TestMethod]
|
||||
public void Test_HashCodeOfT_ManagedType_TestRepeat()
|
||||
|
|
|
@ -37,7 +37,7 @@ namespace UnitTests.HighPerformance.Helpers
|
|||
}
|
||||
}
|
||||
|
||||
#if NETCOREAPP3_0
|
||||
#if NETCOREAPP3_1
|
||||
[TestCategory("ParallelHelper")]
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentException))]
|
||||
|
|
|
@ -48,7 +48,7 @@ namespace UnitTests.HighPerformance.Helpers
|
|||
}
|
||||
}
|
||||
|
||||
#if NETCOREAPP3_0
|
||||
#if NETCOREAPP3_1
|
||||
[TestCategory("ParallelHelper")]
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentException))]
|
||||
|
|
|
@ -174,14 +174,13 @@ namespace UnitTests.HighPerformance.Streams
|
|||
Assert.AreEqual(stream.Position, data.Length);
|
||||
Assert.IsTrue(data.SequenceEqual(result));
|
||||
|
||||
Assert.ThrowsException<InvalidOperationException>(() => stream.WriteByte(128));
|
||||
Assert.ThrowsException<ArgumentException>(() => stream.WriteByte(128));
|
||||
|
||||
int exitCode = stream.ReadByte();
|
||||
|
||||
Assert.AreEqual(exitCode, -1);
|
||||
}
|
||||
|
||||
#if NETCOREAPP2_1 || NETCOREAPP3_0
|
||||
[TestCategory("MemoryStream")]
|
||||
[TestMethod]
|
||||
public void Test_MemoryStream_ReadWrite_Span()
|
||||
|
@ -190,6 +189,9 @@ namespace UnitTests.HighPerformance.Streams
|
|||
|
||||
Memory<byte> data = CreateRandomData(64);
|
||||
|
||||
// This will use the extension when on .NET Standard 2.0,
|
||||
// as the Stream class doesn't have Spam<T> or Memory<T>
|
||||
// public APIs there. This is the case eg. on UWP as well.
|
||||
stream.Write(data.Span);
|
||||
|
||||
Assert.AreEqual(stream.Position, data.Length);
|
||||
|
@ -227,7 +229,6 @@ namespace UnitTests.HighPerformance.Streams
|
|||
Assert.AreEqual(stream.Position, data.Length);
|
||||
Assert.IsTrue(data.Span.SequenceEqual(result.Span));
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Creates a random <see cref="byte"/> array filled with random data.
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
#if NETCOREAPP3_0
|
||||
#if NETCOREAPP3_1
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
@ -31,20 +31,78 @@ namespace UnitTests.HighPerformance
|
|||
[TestMethod]
|
||||
public void Test_NullableReadOnlyRefOfT_CreateNullableReadOnlyRefOfT_Null()
|
||||
{
|
||||
NullableReadOnlyRef<int> reference = default;
|
||||
Assert.IsFalse(default(NullableReadOnlyRef<int>).HasValue);
|
||||
Assert.IsFalse(NullableReadOnlyRef<int>.Null.HasValue);
|
||||
|
||||
Assert.IsFalse(reference.HasValue);
|
||||
Assert.IsFalse(default(NullableReadOnlyRef<string>).HasValue);
|
||||
Assert.IsFalse(NullableReadOnlyRef<string>.Null.HasValue);
|
||||
}
|
||||
|
||||
[TestCategory("NullableReadOnlyRefOfT")]
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(NullReferenceException))]
|
||||
[ExpectedException(typeof(InvalidOperationException))]
|
||||
public void Test_NullableReadOnlyRefOfT_CreateNullableReadOnlyRefOfT_Null_Exception()
|
||||
{
|
||||
NullableReadOnlyRef<int> reference = default;
|
||||
|
||||
_ = reference.Value;
|
||||
}
|
||||
|
||||
[TestCategory("NullableReadOnlyRefOfT")]
|
||||
[TestMethod]
|
||||
public void Test_NullableReadOnlyRefOfT_CreateNullableReadOnlyRefOfT_ImplicitRefCast()
|
||||
{
|
||||
int value = 42;
|
||||
var reference = new Ref<int>(ref value);
|
||||
NullableReadOnlyRef<int> nullableRef = reference;
|
||||
|
||||
Assert.IsTrue(nullableRef.HasValue);
|
||||
Assert.IsTrue(Unsafe.AreSame(ref reference.Value, ref Unsafe.AsRef(nullableRef.Value)));
|
||||
}
|
||||
|
||||
[TestCategory("NullableReadOnlyRefOfT")]
|
||||
[TestMethod]
|
||||
public void Test_NullableReadOnlyRefOfT_CreateNullableReadOnlyRefOfT_ImplicitReadOnlyRefCast()
|
||||
{
|
||||
int value = 42;
|
||||
var reference = new ReadOnlyRef<int>(value);
|
||||
NullableReadOnlyRef<int> nullableRef = reference;
|
||||
|
||||
Assert.IsTrue(nullableRef.HasValue);
|
||||
Assert.IsTrue(Unsafe.AreSame(ref Unsafe.AsRef(reference.Value), ref Unsafe.AsRef(nullableRef.Value)));
|
||||
}
|
||||
|
||||
[TestCategory("NullableReadOnlyRefOfT")]
|
||||
[TestMethod]
|
||||
public void Test_NullableReadOnlyRefOfT_CreateNullableReadOnlyRefOfT_ImplicitNullableRefCast()
|
||||
{
|
||||
int value = 42;
|
||||
var reference = new NullableRef<int>(ref value);
|
||||
NullableReadOnlyRef<int> nullableRef = reference;
|
||||
|
||||
Assert.IsTrue(nullableRef.HasValue);
|
||||
Assert.IsTrue(Unsafe.AreSame(ref reference.Value, ref Unsafe.AsRef(nullableRef.Value)));
|
||||
}
|
||||
|
||||
[TestCategory("NullableReadOnlyRefOfT")]
|
||||
[TestMethod]
|
||||
public void Test_NullableReadOnlyRefOfT_CreateNullableReadOnlyRefOfT_ExplicitCastOfT()
|
||||
{
|
||||
int value = 42;
|
||||
var reference = new NullableRef<int>(ref value);
|
||||
|
||||
Assert.AreEqual(value, (int)reference);
|
||||
}
|
||||
|
||||
[TestCategory("NullableReadOnlyRefOfT")]
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(InvalidOperationException))]
|
||||
public void Test_NullableReadOnlyRefOfT_CreateNullableReadOnlyRefOfT_ExplicitCastOfT_Exception()
|
||||
{
|
||||
NullableRef<int> invalid = default;
|
||||
|
||||
_ = (int)invalid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
#if NETCOREAPP3_0
|
||||
#if NETCOREAPP3_1
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
@ -18,7 +18,7 @@ namespace UnitTests.HighPerformance
|
|||
{
|
||||
[TestCategory("NullableRefOfT")]
|
||||
[TestMethod]
|
||||
public void Test_RefOfT_CreateNullableRefOfT_Ok()
|
||||
public void Test_NullableRefOfT_CreateNullableRefOfT_Ok()
|
||||
{
|
||||
int value = 1;
|
||||
var reference = new NullableRef<int>(ref value);
|
||||
|
@ -33,22 +33,56 @@ namespace UnitTests.HighPerformance
|
|||
|
||||
[TestCategory("NullableRefOfT")]
|
||||
[TestMethod]
|
||||
public void Test_RefOfT_CreateNullableRefOfT_Null()
|
||||
public void Test_NullableRefOfT_CreateNullableRefOfT_Null()
|
||||
{
|
||||
NullableRef<int> reference = default;
|
||||
Assert.IsFalse(default(NullableRef<int>).HasValue);
|
||||
Assert.IsFalse(NullableRef<int>.Null.HasValue);
|
||||
|
||||
Assert.IsFalse(reference.HasValue);
|
||||
Assert.IsFalse(default(NullableRef<string>).HasValue);
|
||||
Assert.IsFalse(NullableRef<string>.Null.HasValue);
|
||||
}
|
||||
|
||||
[TestCategory("NullableRefOfT")]
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(NullReferenceException))]
|
||||
public void Test_RefOfT_CreateNullableRefOfT_Null_Exception()
|
||||
[ExpectedException(typeof(InvalidOperationException))]
|
||||
public void Test_NullableRefOfT_CreateNullableRefOfT_Null_Exception()
|
||||
{
|
||||
NullableRef<int> reference = default;
|
||||
|
||||
_ = reference.Value;
|
||||
}
|
||||
|
||||
[TestCategory("NullableRefOfT")]
|
||||
[TestMethod]
|
||||
public void Test_NullableRefOfT_CreateNullableRefOfT_ImplicitRefCast()
|
||||
{
|
||||
int value = 42;
|
||||
var reference = new Ref<int>(ref value);
|
||||
NullableRef<int> nullableRef = reference;
|
||||
|
||||
Assert.IsTrue(nullableRef.HasValue);
|
||||
Assert.IsTrue(Unsafe.AreSame(ref reference.Value, ref nullableRef.Value));
|
||||
}
|
||||
|
||||
[TestCategory("NullableRefOfT")]
|
||||
[TestMethod]
|
||||
public void Test_NullableRefOfT_CreateNullableRefOfT_ExplicitCastOfT()
|
||||
{
|
||||
int value = 42;
|
||||
var reference = new NullableRef<int>(ref value);
|
||||
|
||||
Assert.AreEqual(value, (int)reference);
|
||||
}
|
||||
|
||||
[TestCategory("NullableRefOfT")]
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(InvalidOperationException))]
|
||||
public void Test_NullableRefOfT_CreateNullableRefOfT_ExplicitCastOfT_Exception()
|
||||
{
|
||||
NullableRef<int> invalid = default;
|
||||
|
||||
_ = (int)invalid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ namespace UnitTests.HighPerformance
|
|||
{
|
||||
[TestCategory("ReadOnlyRefOfT")]
|
||||
[TestMethod]
|
||||
#if NETCOREAPP2_1 || WINDOWS_UWP
|
||||
#if WINDOWS_UWP
|
||||
public void Test_RefOfT_CreateRefOfT()
|
||||
{
|
||||
var model = new ReadOnlyFieldOwner();
|
||||
|
|
|
@ -15,7 +15,7 @@ namespace UnitTests.HighPerformance
|
|||
{
|
||||
[TestCategory("RefOfT")]
|
||||
[TestMethod]
|
||||
#if NETCOREAPP2_1 || WINDOWS_UWP
|
||||
#if WINDOWS_UWP
|
||||
public void Test_RefOfT_CreateRefOfT()
|
||||
{
|
||||
var model = new FieldOwner { Value = 1 };
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
<Import_RootNamespace>UnitTests.HighPerformance.Shared</Import_RootNamespace>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Buffers\Test_MemoryBufferWriter{T}.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Buffers\Test_ArrayPoolBufferWriter{T}.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Buffers\Test_MemoryOwner{T}.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Buffers\Test_SpanOwner{T}.cs" />
|
||||
|
@ -21,6 +22,8 @@
|
|||
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_ObjectExtensions.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_BoolExtensions.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_HashCodeExtensions.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_IBufferWriterExtensions.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_StreamExtensions.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_SpanExtensions.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_ReadOnlySpanExtensions.Count.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_ReadOnlySpanExtensions.cs" />
|
||||
|
|
|
@ -34,10 +34,11 @@ Once you do a search, you should see a list similar to the one below (versions m
|
|||
| NuGet Package Name | Description |
|
||||
| --- | --- |
|
||||
| Microsoft.Toolkit | .NET Standard NuGet package containing common code |
|
||||
| Microsoft.Toolkit.HighPerformance | .NET Standard and .NET Core NuGet package with performance oriented helpers, extensions, etc. |
|
||||
| Microsoft.Toolkit.Parsers | .NET Standard NuGet package containing cross-platform parsers, such as Markdown and RSS |
|
||||
| Microsoft.Toolkit.Services | .NET Standard NuGet package containing cross-platform services |
|
||||
| Microsoft.Toolkit.Uwp | Main NuGet package includes code only helpers such as Colors conversion tool, Storage file handling, a Stream helper class, etc. |
|
||||
| Microsoft.Toolkit.Uwp.Notifications | Notifications Package - Generate tile, toast, and badge notifications for Windows 10 via code. Includes intellisense support to avoid having to use the XML syntax. |
|
||||
| Microsoft.Toolkit.Uwp.Notifications | Notifications Package - Generate tile, toast, and badge notifications for Windows 10 via code. Includes intellisense support to avoid having to use the XML syntax. |
|
||||
| Microsoft.Toolkit.Uwp.Notifications.Javascript | Notification Packages for JavaScript |
|
||||
| Microsoft.Toolkit.Uwp.Services | Services Package - This NuGet package includes the service helpers for Facebook, LinkedIn, Microsoft Graph, Twitter and more |
|
||||
| Microsoft.Toolkit.Uwp.UI | UI Packages - Brushes, XAML converters, Visual tree extensions, and other extensions and helpers for your XAML UI. |
|
||||
|
|
Загрузка…
Ссылка в новой задаче