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:
Sergio Pedri 2020-05-13 21:32:20 +02:00 коммит произвёл GitHub
Родитель b24953ac8a
Коммит 059cf83f1f
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
76 изменённых файлов: 3493 добавлений и 1099 удалений

Просмотреть файл

@ -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&lt;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&lt;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&lt;T&gt;: an IBufferWriter&lt;T&gt; implementation using pooled arrays, which also supports IMemoryOwner&lt;T&gt;.
- MemoryBufferWriter&lt;T&gt;: an IBufferWriter&lt;T&gt;: implementation that can wrap external Memory&lt;T&gt;: instances.
- MemoryOwner&lt;T&gt;: an IMemoryOwner&lt;T&gt; implementation with an embedded length and a fast Span&lt;T&gt; accessor.
- SpanOwner&lt;T&gt;: a stack-only type with the ability to rent a buffer of a specified length and getting a Span&lt;T&gt; from it.
- String, array, Span&lt;T&gt;, Memory&lt;T&gt; 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&lt;T&gt;: a type mapping boxed value types and exposing some utility and high performance methods.
- Ref&lt;T&gt;: 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&lt;T&gt;: a stack-only struct similar to Ref&lt;T&gt;, 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. |