1
0
mirror of https://github.com/oliverbooth/X10D synced 2024-11-23 00:58:48 +00:00

perf: fix performance of intrisics

This changes removes the CPU-arch support provider interfaces that were introduced with 87b6dbdd56. That commit worsened the performance of the intrinsic methods because it resulted in a box when upcasting the System_SupportProvider value type to an interface, removing the potential for JIT to optimise the code path.
This commit is contained in:
Oliver Booth 2023-04-02 15:17:28 +01:00
parent 77836d51fc
commit fdc0c6aa35
No known key found for this signature in database
GPG Key ID: 20BEB9DC87961025
14 changed files with 243 additions and 275 deletions

View File

@ -1,5 +1,5 @@
using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Runtime.Intrinsics.X86;
using Moq; using Microsoft.VisualStudio.TestTools.UnitTesting;
using X10D.Collections; using X10D.Collections;
namespace X10D.Tests.Collections; namespace X10D.Tests.Collections;
@ -10,7 +10,8 @@ public class ByteTests
[TestMethod] [TestMethod]
public void Unpack_ShouldUnpackToArrayCorrectly() public void Unpack_ShouldUnpackToArrayCorrectly()
{ {
bool[] bits = ((byte)0b11010100).Unpack(); const byte value = 0b11010100;
bool[] bits = value.Unpack();
Assert.AreEqual(8, bits.Length); Assert.AreEqual(8, bits.Length);
@ -27,8 +28,9 @@ public class ByteTests
[TestMethod] [TestMethod]
public void Unpack_ShouldUnpackToSpanCorrectly() public void Unpack_ShouldUnpackToSpanCorrectly()
{ {
const byte value = 0b11010100;
Span<bool> bits = stackalloc bool[8]; Span<bool> bits = stackalloc bool[8];
((byte)0b11010100).Unpack(bits); value.Unpack(bits);
Assert.IsFalse(bits[0]); Assert.IsFalse(bits[0]);
Assert.IsFalse(bits[1]); Assert.IsFalse(bits[1]);
@ -41,14 +43,35 @@ public class ByteTests
} }
#if NET5_0_OR_GREATER #if NET5_0_OR_GREATER
[TestMethod]
public void Unpack_ShouldUnpackToSpanCorrectly_GivenFallbackImplementation()
{
var mock = new Mock<ISsse3SupportProvider>();
mock.Setup(provider => provider.IsSupported).Returns(false);
[TestMethod]
public void UnpackInternal_Fallback_ShouldUnpackToSpanCorrectly()
{
const byte value = 0b11010100;
Span<bool> bits = stackalloc bool[8]; Span<bool> bits = stackalloc bool[8];
((byte)0b11010100).UnpackInternal(bits, mock.Object); value.UnpackInternal_Fallback(bits);
Assert.IsFalse(bits[0]);
Assert.IsFalse(bits[1]);
Assert.IsTrue(bits[2]);
Assert.IsFalse(bits[3]);
Assert.IsTrue(bits[4]);
Assert.IsFalse(bits[5]);
Assert.IsTrue(bits[6]);
Assert.IsTrue(bits[7]);
}
[TestMethod]
public void UnpackInternal_Ssse3_ShouldUnpackToSpanCorrectly()
{
if (!Sse3.IsSupported)
{
return;
}
const byte value = 0b11010100;
Span<bool> bits = stackalloc bool[8];
value.UnpackInternal_Ssse3(bits);
Assert.IsFalse(bits[0]); Assert.IsFalse(bits[0]);
Assert.IsFalse(bits[1]); Assert.IsFalse(bits[1]);
@ -64,7 +87,8 @@ public class ByteTests
[TestMethod] [TestMethod]
public void Unpack_ShouldRepackEqually() public void Unpack_ShouldRepackEqually()
{ {
Assert.AreEqual(0b11010100, ((byte)0b11010100).Unpack().PackByte()); const byte value = 0b11010100;
Assert.AreEqual(value, value.Unpack().PackByte());
} }
[TestMethod] [TestMethod]
@ -72,8 +96,9 @@ public class ByteTests
{ {
Assert.ThrowsException<ArgumentException>(() => Assert.ThrowsException<ArgumentException>(() =>
{ {
const byte value = 0b11010100;
Span<bool> bits = stackalloc bool[0]; Span<bool> bits = stackalloc bool[0];
((byte)0b11010100).Unpack(bits); value.Unpack(bits);
}); });
} }
} }

View File

@ -1,5 +1,5 @@
using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Runtime.Intrinsics.X86;
using Moq; using Microsoft.VisualStudio.TestTools.UnitTesting;
using X10D.Collections; using X10D.Collections;
namespace X10D.Tests.Collections; namespace X10D.Tests.Collections;
@ -10,7 +10,8 @@ public class Int16Tests
[TestMethod] [TestMethod]
public void Unpack_ShouldUnpackToArrayCorrectly() public void Unpack_ShouldUnpackToArrayCorrectly()
{ {
bool[] bits = ((short)0b11010100).Unpack(); const short value = 0b11010100;
bool[] bits = value.Unpack();
Assert.AreEqual(16, bits.Length); Assert.AreEqual(16, bits.Length);
@ -32,8 +33,31 @@ public class Int16Tests
[TestMethod] [TestMethod]
public void Unpack_ShouldUnpackToSpanCorrectly() public void Unpack_ShouldUnpackToSpanCorrectly()
{ {
const short value = 0b11010100;
Span<bool> bits = stackalloc bool[16]; Span<bool> bits = stackalloc bool[16];
((short)0b11010100).Unpack(bits); value.Unpack(bits);
Assert.IsFalse(bits[0]);
Assert.IsFalse(bits[1]);
Assert.IsTrue(bits[2]);
Assert.IsFalse(bits[3]);
Assert.IsTrue(bits[4]);
Assert.IsFalse(bits[5]);
Assert.IsTrue(bits[6]);
Assert.IsTrue(bits[7]);
for (var index = 8; index < 16; index++)
{
Assert.IsFalse(bits[index]);
}
}
[TestMethod]
public void Unpack_ShouldUnpackToSpanCorrectly_GivenFallbackImplementation()
{
const short value = 0b11010100;
Span<bool> bits = stackalloc bool[16];
value.UnpackInternal_Fallback(bits);
Assert.IsFalse(bits[0]); Assert.IsFalse(bits[0]);
Assert.IsFalse(bits[1]); Assert.IsFalse(bits[1]);
@ -52,13 +76,16 @@ public class Int16Tests
#if NET5_0_OR_GREATER #if NET5_0_OR_GREATER
[TestMethod] [TestMethod]
public void Unpack_ShouldUnpackToSpanCorrectly_GivenFallbackImplementation() public void UnpackInternal_Ssse3_ShouldUnpackToSpanCorrectly()
{ {
var mock = new Mock<ISsse3SupportProvider>(); if (!Sse3.IsSupported)
mock.Setup(provider => provider.IsSupported).Returns(false); {
return;
}
const short value = 0b11010100;
Span<bool> bits = stackalloc bool[16]; Span<bool> bits = stackalloc bool[16];
((short)0b11010100).UnpackInternal(bits, mock.Object); value.UnpackInternal_Ssse3(bits);
Assert.IsFalse(bits[0]); Assert.IsFalse(bits[0]);
Assert.IsFalse(bits[1]); Assert.IsFalse(bits[1]);
@ -79,7 +106,8 @@ public class Int16Tests
[TestMethod] [TestMethod]
public void Unpack_ShouldRepackEqually() public void Unpack_ShouldRepackEqually()
{ {
Assert.AreEqual(0b11010100, ((short)0b11010100).Unpack().PackInt16()); const short value = 0b11010100;
Assert.AreEqual(value, value.Unpack().PackInt16());
} }
[TestMethod] [TestMethod]
@ -87,8 +115,9 @@ public class Int16Tests
{ {
Assert.ThrowsException<ArgumentException>(() => Assert.ThrowsException<ArgumentException>(() =>
{ {
const short value = 0b11010100;
Span<bool> bits = stackalloc bool[0]; Span<bool> bits = stackalloc bool[0];
((short)0b11010100).Unpack(bits); value.Unpack(bits);
}); });
} }
} }

View File

@ -1,5 +1,5 @@
using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Runtime.Intrinsics.X86;
using Moq; using Microsoft.VisualStudio.TestTools.UnitTesting;
using X10D.Collections; using X10D.Collections;
namespace X10D.Tests.Collections; namespace X10D.Tests.Collections;
@ -10,7 +10,8 @@ public class Int32Tests
[TestMethod] [TestMethod]
public void Unpack_ShouldUnpackToArrayCorrectly() public void Unpack_ShouldUnpackToArrayCorrectly()
{ {
bool[] bits = 0b11010100.Unpack(); const int value = 0b11010100;
bool[] bits = value.Unpack();
Assert.AreEqual(32, bits.Length); Assert.AreEqual(32, bits.Length);
@ -32,8 +33,31 @@ public class Int32Tests
[TestMethod] [TestMethod]
public void Unpack_ShouldUnpackToSpanCorrectly() public void Unpack_ShouldUnpackToSpanCorrectly()
{ {
const int value = 0b11010100;
Span<bool> bits = stackalloc bool[32]; Span<bool> bits = stackalloc bool[32];
0b11010100.Unpack(bits); value.Unpack(bits);
Assert.IsFalse(bits[0]);
Assert.IsFalse(bits[1]);
Assert.IsTrue(bits[2]);
Assert.IsFalse(bits[3]);
Assert.IsTrue(bits[4]);
Assert.IsFalse(bits[5]);
Assert.IsTrue(bits[6]);
Assert.IsTrue(bits[7]);
for (var index = 8; index < 32; index++)
{
Assert.IsFalse(bits[index]);
}
}
[TestMethod]
public void UnpackInternal_Fallback_ShouldUnpackToSpanCorrectly()
{
const int value = 0b11010100;
Span<bool> bits = stackalloc bool[32];
value.UnpackInternal_Fallback(bits);
Assert.IsFalse(bits[0]); Assert.IsFalse(bits[0]);
Assert.IsFalse(bits[1]); Assert.IsFalse(bits[1]);
@ -52,15 +76,16 @@ public class Int32Tests
#if NET5_0_OR_GREATER #if NET5_0_OR_GREATER
[TestMethod] [TestMethod]
public void Unpack_ShouldUnpackToSpanCorrectly_GivenFallbackFromAvx2() public void UnpackInternal_Ssse3_ShouldUnpackToSpanCorrectly()
{ {
var ssse3Mock = new Mock<ISsse3SupportProvider>(); if (!Ssse3.IsSupported)
var avx2Mock = new Mock<IAvx2SupportProvider>(); {
avx2Mock.Setup(provider => provider.IsSupported).Returns(false); return;
ssse3Mock.Setup(provider => provider.IsSupported).Returns(true); }
const int value = 0b11010100;
Span<bool> bits = stackalloc bool[32]; Span<bool> bits = stackalloc bool[32];
0b11010100.UnpackInternal(bits, ssse3Mock.Object, avx2Mock.Object); value.UnpackInternal_Ssse3(bits);
Assert.IsFalse(bits[0]); Assert.IsFalse(bits[0]);
Assert.IsFalse(bits[1]); Assert.IsFalse(bits[1]);
@ -78,15 +103,16 @@ public class Int32Tests
} }
[TestMethod] [TestMethod]
public void Unpack_ShouldUnpackToSpanCorrectly_GivenFallback() public void UnpackInternal_Avx2_ShouldUnpackToSpanCorrectly()
{ {
var ssse3Mock = new Mock<ISsse3SupportProvider>(); if (!Avx2.IsSupported)
var avx2Mock = new Mock<IAvx2SupportProvider>(); {
ssse3Mock.Setup(provider => provider.IsSupported).Returns(false); return;
avx2Mock.Setup(provider => provider.IsSupported).Returns(false); }
const int value = 0b11010100;
Span<bool> bits = stackalloc bool[32]; Span<bool> bits = stackalloc bool[32];
0b11010100.UnpackInternal(bits, ssse3Mock.Object, avx2Mock.Object); value.UnpackInternal_Avx2(bits);
Assert.IsFalse(bits[0]); Assert.IsFalse(bits[0]);
Assert.IsFalse(bits[1]); Assert.IsFalse(bits[1]);
@ -102,13 +128,13 @@ public class Int32Tests
Assert.IsFalse(bits[index]); Assert.IsFalse(bits[index]);
} }
} }
#endif #endif
[TestMethod] [TestMethod]
public void Unpack_ShouldRepackEqually() public void Unpack_ShouldRepackEqually()
{ {
Assert.AreEqual(0b11010100, 0b11010100.Unpack().PackInt32()); const int value = 0b11010100;
Assert.AreEqual(value, value.Unpack().PackInt32());
} }
[TestMethod] [TestMethod]
@ -116,8 +142,9 @@ public class Int32Tests
{ {
Assert.ThrowsException<ArgumentException>(() => Assert.ThrowsException<ArgumentException>(() =>
{ {
const int value = 0b11010100;
Span<bool> bits = stackalloc bool[0]; Span<bool> bits = stackalloc bool[0];
0b11010100.Unpack(bits); value.Unpack(bits);
}); });
} }
} }

View File

@ -1,7 +1,7 @@
#if NET6_0_OR_GREATER #if NET6_0_OR_GREATER
using System.Runtime.Intrinsics; using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using X10D.Core; using X10D.Core;
namespace X10D.Tests.Core; namespace X10D.Tests.Core;
@ -12,9 +12,6 @@ public class IntrinsicTests
[TestMethod] [TestMethod]
public void CorrectBoolean_ShouldReturnExpectedVector64Result_GivenInputVector() public void CorrectBoolean_ShouldReturnExpectedVector64Result_GivenInputVector()
{ {
var mock = new Mock<ISse2SupportProvider>();
mock.Setup(provider => provider.IsSupported).Returns(true);
var inputVector = Vector64.Create(0, 1, 2, 0, 3, 0, 0, (byte)4); var inputVector = Vector64.Create(0, 1, 2, 0, 3, 0, 0, (byte)4);
var expectedResult = Vector64.Create(0, 1, 1, 0, 1, 0, 0, (byte)1); var expectedResult = Vector64.Create(0, 1, 1, 0, 1, 0, 0, (byte)1);
@ -24,89 +21,86 @@ public class IntrinsicTests
} }
[TestMethod] [TestMethod]
public void CorrectBoolean_ShouldReturnExpectedVector128Result_GivenInputVector() public void CorrectBooleanInternal_Fallback_ShouldReturnExpectedVector128Result_GivenInputVector()
{ {
var mock = new Mock<ISse2SupportProvider>();
mock.Setup(provider => provider.IsSupported).Returns(true);
var inputVector = Vector128.Create(0, 1, 2, 0, 3, 0, 0, 4, 5, 0, 0, 6, 0, 0, 7, (byte)8); var inputVector = Vector128.Create(0, 1, 2, 0, 3, 0, 0, 4, 5, 0, 0, 6, 0, 0, 7, (byte)8);
var expectedResult = Vector128.Create(0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, (byte)1); var expectedResult = Vector128.Create(0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, (byte)1);
Vector128<byte> result = inputVector.CorrectBooleanInternal(mock.Object); Vector128<byte> result = inputVector.CorrectBooleanInternal_Fallback();
Assert.AreEqual(expectedResult, result); Assert.AreEqual(expectedResult, result);
} }
[TestMethod] [TestMethod]
public void CorrectBoolean_ShouldReturnExpectedVector128Result_WhenSse2NotSupported() public void CorrectBooleanInternal_Sse2_ShouldReturnExpectedVector128Result_GivenInputVector()
{ {
var mock = new Mock<ISse2SupportProvider>(); if (!Sse2.IsSupported)
mock.Setup(provider => provider.IsSupported).Returns(false); {
return;
}
var inputVector = Vector128.Create(0, 1, 2, 0, 3, 0, 0, 4, 5, 0, 0, 6, 0, 0, 7, (byte)8); var inputVector = Vector128.Create(0, 1, 2, 0, 3, 0, 0, 4, 5, 0, 0, 6, 0, 0, 7, (byte)8);
var expectedResult = Vector128.Create(0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, (byte)1); var expectedResult = Vector128.Create(0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, (byte)1);
Vector128<byte> result = inputVector.CorrectBooleanInternal(mock.Object); Vector128<byte> result = inputVector.CorrectBooleanInternal_Sse2();
Assert.AreEqual(expectedResult, result); Assert.AreEqual(expectedResult, result);
} }
[TestMethod] [TestMethod]
public void CorrectBoolean_ShouldReturnExpectedVector256Result_GivenInputVector() public void CorrectBooleanInternal_Avx2_ShouldReturnExpectedVector256Result_GivenInputVector()
{ {
var mock = new Mock<IAvx2SupportProvider>(); if (!Avx2.IsSupported)
mock.Setup(provider => provider.IsSupported).Returns(true); {
return;
}
var inputVector = Vector256.Create(0, 1, 2, 0, 3, 0, 0, 4, 5, 0, 0, 6, 0, 0, 7, 8, 0, 1, 2, 0, 3, 0, 0, 4, 5, 0, 0, 6, 0, var inputVector = Vector256.Create(0, 1, 2, 0, 3, 0, 0, 4, 5, 0, 0, 6, 0, 0, 7, 8, 0, 1, 2, 0, 3, 0, 0, 4, 5, 0, 0, 6, 0,
0, 7, (byte)8); 0, 7, (byte)8);
var expectedResult = Vector256.Create(0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, var expectedResult = Vector256.Create(0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1,
0, 0, 1, (byte)1); 0, 0, 1, (byte)1);
Vector256<byte> result = inputVector.CorrectBooleanInternal(mock.Object); Vector256<byte> result = inputVector.CorrectBooleanInternal_Avx2();
Assert.AreEqual(expectedResult, result); Assert.AreEqual(expectedResult, result);
} }
[TestMethod] [TestMethod]
public void CorrectBoolean_ShouldReturnExpectedVector256Result_WhenSse2NotSupported() public void CorrectBooleanInternal_Fallback_ShouldReturnExpectedVector256Result_GivenInputVector()
{ {
var mock = new Mock<IAvx2SupportProvider>();
mock.Setup(provider => provider.IsSupported).Returns(false);
var inputVector = Vector256.Create(0, 1, 2, 0, 3, 0, 0, 4, 5, 0, 0, 6, 0, 0, 7, 8, 0, 1, 2, 0, 3, 0, 0, 4, 5, 0, 0, 6, 0, var inputVector = Vector256.Create(0, 1, 2, 0, 3, 0, 0, 4, 5, 0, 0, 6, 0, 0, 7, 8, 0, 1, 2, 0, 3, 0, 0, 4, 5, 0, 0, 6, 0,
0, 7, (byte)8); 0, 7, (byte)8);
var expectedResult = Vector256.Create(0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, var expectedResult = Vector256.Create(0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1,
0, 0, 1, (byte)1); 0, 0, 1, (byte)1);
Vector256<byte> result = inputVector.CorrectBooleanInternal(mock.Object); Vector256<byte> result = inputVector.CorrectBooleanInternal_Fallback();
Assert.AreEqual(expectedResult, result); Assert.AreEqual(expectedResult, result);
} }
[TestMethod] [TestMethod]
public void ReverseElements_ShouldReturnExpectedVector128Result_GivenInputVector() public void ReverseElementsInternal_Fallback_ShouldReturnExpectedVector128Result_GivenInputVector()
{ {
var mock = new Mock<ISse2SupportProvider>();
mock.Setup(provider => provider.IsSupported).Returns(true);
var inputVector = Vector128.Create(42UL, 69UL); var inputVector = Vector128.Create(42UL, 69UL);
var expectedResult = Vector128.Create(69UL, 42UL); var expectedResult = Vector128.Create(69UL, 42UL);
Vector128<ulong> result = inputVector.ReverseElementsInternal(mock.Object); Vector128<ulong> result = inputVector.ReverseElementsInternal_Fallback();
Assert.AreEqual(expectedResult, result); Assert.AreEqual(expectedResult, result);
} }
[TestMethod] [TestMethod]
public void ReverseElements_ShouldReturnExpectedVector128Result_WhenSse2NotSupported() public void ReverseElementsInternal_Sse2_ShouldReturnExpectedVector128Result_GivenInputVector()
{ {
var mock = new Mock<ISse2SupportProvider>(); if (!Sse2.IsSupported)
mock.Setup(provider => provider.IsSupported).Returns(false); {
return;
}
var inputVector = Vector128.Create(42UL, 69UL); var inputVector = Vector128.Create(42UL, 69UL);
var expectedResult = Vector128.Create(69UL, 42UL); var expectedResult = Vector128.Create(69UL, 42UL);
Vector128<ulong> result = inputVector.ReverseElementsInternal(mock.Object); Vector128<ulong> result = inputVector.ReverseElementsInternal_Sse2();
Assert.AreEqual(expectedResult, result); Assert.AreEqual(expectedResult, result);
} }

View File

@ -1,5 +1,6 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts; using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
#if NETCOREAPP3_0_OR_GREATER #if NETCOREAPP3_0_OR_GREATER
using System.Runtime.Intrinsics; using System.Runtime.Intrinsics;
@ -21,6 +22,11 @@ public static class ByteExtensions
/// <param name="value">The value to unpack.</param> /// <param name="value">The value to unpack.</param>
/// <returns>An array of <see cref="bool" /> with length 8.</returns> /// <returns>An array of <see cref="bool" /> with length 8.</returns>
[Pure] [Pure]
#if NETCOREAPP3_1_OR_GREATER
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
#else
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
public static bool[] Unpack(this byte value) public static bool[] Unpack(this byte value)
{ {
var buffer = new bool[Size]; var buffer = new bool[Size];
@ -35,20 +41,12 @@ public static class ByteExtensions
/// <param name="destination">When this method returns, contains the unpacked booleans from <paramref name="value" />.</param> /// <param name="destination">When this method returns, contains the unpacked booleans from <paramref name="value" />.</param>
/// <exception cref="ArgumentException"><paramref name="destination" /> is not large enough to contain the result.</exception> /// <exception cref="ArgumentException"><paramref name="destination" /> is not large enough to contain the result.</exception>
[ExcludeFromCodeCoverage] [ExcludeFromCodeCoverage]
#if NETCOREAPP3_1_OR_GREATER
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
#else
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
public static void Unpack(this byte value, Span<bool> destination) public static void Unpack(this byte value, Span<bool> destination)
{
#if NETCOREAPP3_0_OR_GREATER
UnpackInternal(value, destination, new SystemSsse3SupportProvider());
#else
UnpackInternal(value, destination);
#endif
}
#if NETCOREAPP3_0_OR_GREATER
internal static void UnpackInternal(this byte value, Span<bool> destination, ISsse3SupportProvider? ssse3SupportProvider)
#else
internal static void UnpackInternal(this byte value, Span<bool> destination)
#endif
{ {
if (destination.Length < Size) if (destination.Length < Size)
{ {
@ -56,9 +54,7 @@ public static class ByteExtensions
} }
#if NETCOREAPP3_0_OR_GREATER #if NETCOREAPP3_0_OR_GREATER
ssse3SupportProvider ??= new SystemSsse3SupportProvider(); if (Sse3.IsSupported)
if (ssse3SupportProvider.IsSupported)
{ {
UnpackInternal_Ssse3(value, destination); UnpackInternal_Ssse3(value, destination);
return; return;
@ -68,7 +64,12 @@ public static class ByteExtensions
UnpackInternal_Fallback(value, destination); UnpackInternal_Fallback(value, destination);
} }
private static void UnpackInternal_Fallback(byte value, Span<bool> destination) #if NETCOREAPP3_1_OR_GREATER
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
#else
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
internal static void UnpackInternal_Fallback(this byte value, Span<bool> destination)
{ {
for (var index = 0; index < Size; index++) for (var index = 0; index < Size; index++)
{ {
@ -77,8 +78,12 @@ public static class ByteExtensions
} }
#if NETCOREAPP3_0_OR_GREATER #if NETCOREAPP3_0_OR_GREATER
#if NETCOREAPP3_1_OR_GREATER
private unsafe static void UnpackInternal_Ssse3(byte value, Span<bool> destination) [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
#else
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
internal unsafe static void UnpackInternal_Ssse3(this byte value, Span<bool> destination)
{ {
fixed (bool* pDestination = destination) fixed (bool* pDestination = destination)
{ {

View File

@ -1,4 +1,6 @@
using System.Diagnostics.Contracts; using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
#if NETCOREAPP3_0_OR_GREATER #if NETCOREAPP3_0_OR_GREATER
using System.Runtime.Intrinsics; using System.Runtime.Intrinsics;
@ -20,6 +22,11 @@ public static class Int16Extensions
/// <param name="value">The value to unpack.</param> /// <param name="value">The value to unpack.</param>
/// <returns>An array of <see cref="bool" /> with length 16.</returns> /// <returns>An array of <see cref="bool" /> with length 16.</returns>
[Pure] [Pure]
#if NETCOREAPP3_1_OR_GREATER
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
#else
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
public static bool[] Unpack(this short value) public static bool[] Unpack(this short value)
{ {
var ret = new bool[Size]; var ret = new bool[Size];
@ -33,20 +40,13 @@ public static class Int16Extensions
/// <param name="value">The value to unpack.</param> /// <param name="value">The value to unpack.</param>
/// <param name="destination">When this method returns, contains the unpacked booleans from <paramref name="value" />.</param> /// <param name="destination">When this method returns, contains the unpacked booleans from <paramref name="value" />.</param>
/// <exception cref="ArgumentException"><paramref name="destination" /> is not large enough to contain the result.</exception> /// <exception cref="ArgumentException"><paramref name="destination" /> is not large enough to contain the result.</exception>
[ExcludeFromCodeCoverage]
#if NETCOREAPP3_1_OR_GREATER
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
#else
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
public static void Unpack(this short value, Span<bool> destination) public static void Unpack(this short value, Span<bool> destination)
{
#if NETCOREAPP3_0_OR_GREATER
UnpackInternal(value, destination, new SystemSsse3SupportProvider());
#else
UnpackInternal(value, destination);
#endif
}
#if NETCOREAPP3_0_OR_GREATER
internal static void UnpackInternal(this short value, Span<bool> destination, ISsse3SupportProvider? ssse3SupportProvider)
#else
internal static void UnpackInternal(this short value, Span<bool> destination)
#endif
{ {
if (destination.Length < Size) if (destination.Length < Size)
{ {
@ -54,9 +54,7 @@ public static class Int16Extensions
} }
#if NETCOREAPP3_0_OR_GREATER #if NETCOREAPP3_0_OR_GREATER
ssse3SupportProvider ??= new SystemSsse3SupportProvider(); if (Sse3.IsSupported)
if (ssse3SupportProvider.IsSupported)
{ {
UnpackInternal_Ssse3(value, destination); UnpackInternal_Ssse3(value, destination);
return; return;
@ -66,7 +64,12 @@ public static class Int16Extensions
UnpackInternal_Fallback(value, destination); UnpackInternal_Fallback(value, destination);
} }
private static void UnpackInternal_Fallback(short value, Span<bool> destination) #if NETCOREAPP3_1_OR_GREATER
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
#else
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
internal static void UnpackInternal_Fallback(this short value, Span<bool> destination)
{ {
for (var index = 0; index < Size; index++) for (var index = 0; index < Size; index++)
{ {
@ -75,16 +78,12 @@ public static class Int16Extensions
} }
#if NETCOREAPP3_0_OR_GREATER #if NETCOREAPP3_0_OR_GREATER
private struct SystemSsse3SupportProvider : ISsse3SupportProvider #if NETCOREAPP3_1_OR_GREATER
{ [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
/// <inheritdoc /> #else
public bool IsSupported [MethodImpl(MethodImplOptions.AggressiveInlining)]
{ #endif
get => Sse3.IsSupported; internal unsafe static void UnpackInternal_Ssse3(this short value, Span<bool> destination)
}
}
private unsafe static void UnpackInternal_Ssse3(short value, Span<bool> destination)
{ {
fixed (bool* pDestination = destination) fixed (bool* pDestination = destination)
{ {

View File

@ -1,5 +1,6 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts; using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
#if NETCOREAPP3_0_OR_GREATER #if NETCOREAPP3_0_OR_GREATER
using System.Runtime.Intrinsics; using System.Runtime.Intrinsics;
@ -36,22 +37,6 @@ public static class Int32Extensions
/// <exception cref="ArgumentException"><paramref name="destination" /> is not large enough to contain the result.</exception> /// <exception cref="ArgumentException"><paramref name="destination" /> is not large enough to contain the result.</exception>
[ExcludeFromCodeCoverage] [ExcludeFromCodeCoverage]
public static void Unpack(this int value, Span<bool> destination) public static void Unpack(this int value, Span<bool> destination)
{
#if NETCOREAPP3_0_OR_GREATER
UnpackInternal(value, destination, new SystemSsse3SupportProvider(), new SystemAvx2SupportProvider());
#else
UnpackInternal(value, destination);
#endif
}
internal static void UnpackInternal(this int value,
Span<bool> destination
#if NETCOREAPP3_0_OR_GREATER
,
ISsse3SupportProvider? ssse3SupportProvider,
IAvx2SupportProvider? avx2SupportProvider
#endif
)
{ {
if (destination.Length < Size) if (destination.Length < Size)
{ {
@ -59,16 +44,13 @@ public static class Int32Extensions
} }
#if NETCOREAPP3_0_OR_GREATER #if NETCOREAPP3_0_OR_GREATER
ssse3SupportProvider ??= new SystemSsse3SupportProvider(); if (Avx2.IsSupported)
avx2SupportProvider ??= new SystemAvx2SupportProvider();
if (avx2SupportProvider.IsSupported)
{ {
UnpackInternal_Avx2(value, destination); UnpackInternal_Avx2(value, destination);
return; return;
} }
if (ssse3SupportProvider.IsSupported) if (Sse3.IsSupported)
{ {
UnpackInternal_Ssse3(value, destination); UnpackInternal_Ssse3(value, destination);
return; return;
@ -78,7 +60,12 @@ public static class Int32Extensions
UnpackInternal_Fallback(value, destination); UnpackInternal_Fallback(value, destination);
} }
private static void UnpackInternal_Fallback(int value, Span<bool> destination) #if NETCOREAPP3_1_OR_GREATER
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
#else
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
internal static void UnpackInternal_Fallback(this int value, Span<bool> destination)
{ {
for (var index = 0; index < Size; index++) for (var index = 0; index < Size; index++)
{ {
@ -87,7 +74,12 @@ public static class Int32Extensions
} }
#if NETCOREAPP3_0_OR_GREATER #if NETCOREAPP3_0_OR_GREATER
private static unsafe void UnpackInternal_Ssse3(int value, Span<bool> destination) #if NETCOREAPP3_1_OR_GREATER
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
#else
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
internal static unsafe void UnpackInternal_Ssse3(this int value, Span<bool> destination)
{ {
fixed (bool* pDestination = destination) fixed (bool* pDestination = destination)
{ {
@ -117,7 +109,7 @@ public static class Int32Extensions
} }
} }
private static unsafe void UnpackInternal_Avx2(int value, Span<bool> destination) internal static unsafe void UnpackInternal_Avx2(this int value, Span<bool> destination)
{ {
fixed (bool* pDestination = destination) fixed (bool* pDestination = destination)
{ {

View File

@ -71,7 +71,7 @@ public static class IntrinsicExtensions
[ExcludeFromCodeCoverage] [ExcludeFromCodeCoverage]
public static Vector128<byte> CorrectBoolean(this Vector128<byte> vector) public static Vector128<byte> CorrectBoolean(this Vector128<byte> vector)
{ {
return CorrectBooleanInternal(vector, new SystemSse2SupportProvider()); return Sse2.IsSupported ? CorrectBooleanInternal_Sse2(vector) : CorrectBooleanInternal_Fallback(vector);
} }
/// <summary> /// <summary>
@ -94,7 +94,7 @@ public static class IntrinsicExtensions
[ExcludeFromCodeCoverage] [ExcludeFromCodeCoverage]
public static Vector256<byte> CorrectBoolean(this Vector256<byte> vector) public static Vector256<byte> CorrectBoolean(this Vector256<byte> vector)
{ {
return CorrectBooleanInternal(vector, new SystemAvx2SupportProvider()); return Avx2.IsSupported ? CorrectBooleanInternal_Avx2(vector) : CorrectBooleanInternal_Fallback(vector);
} }
/// <summary> /// <summary>
@ -118,26 +118,13 @@ public static class IntrinsicExtensions
[ExcludeFromCodeCoverage] [ExcludeFromCodeCoverage]
public static Vector128<ulong> ReverseElements(this Vector128<ulong> vector) public static Vector128<ulong> ReverseElements(this Vector128<ulong> vector)
{ {
return ReverseElementsInternal(vector, new SystemSse2SupportProvider()); return Sse2.IsSupported ? ReverseElementsInternal_Sse2(vector) : ReverseElementsInternal_Fallback(vector);
} }
[Pure] [Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
internal static Vector128<byte> CorrectBooleanInternal(this Vector128<byte> vector, ISse2SupportProvider? sse2SupportProvider) internal static Vector128<byte> CorrectBooleanInternal_Fallback(this Vector128<byte> vector)
{ {
sse2SupportProvider ??= new SystemSse2SupportProvider();
if (sse2SupportProvider.IsSupported)
{
Vector128<byte> cmp = Sse2.CompareEqual(vector, Vector128<byte>.Zero);
Vector128<byte> result = Sse2.AndNot(cmp, Vector128.Create((byte)1));
return result;
}
// TODO: AdvSimd implementation.
// TODO: WasmSimd implementation.
Vector128<byte> output = IntrinsicUtility.GetUninitializedVector128<byte>(); Vector128<byte> output = IntrinsicUtility.GetUninitializedVector128<byte>();
for (var index = 0; index < Vector128<byte>.Count; index++) for (var index = 0; index < Vector128<byte>.Count; index++)
@ -151,18 +138,18 @@ public static class IntrinsicExtensions
[Pure] [Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
internal static Vector256<byte> CorrectBooleanInternal(this Vector256<byte> vector, IAvx2SupportProvider? supportProvider) internal static Vector128<byte> CorrectBooleanInternal_Sse2(this Vector128<byte> vector)
{ {
supportProvider ??= new SystemAvx2SupportProvider(); Vector128<byte> cmp = Sse2.CompareEqual(vector, Vector128<byte>.Zero);
Vector128<byte> result = Sse2.AndNot(cmp, Vector128.Create((byte)1));
if (supportProvider.IsSupported)
{
Vector256<byte> cmp = Avx2.CompareEqual(vector, Vector256<byte>.Zero);
Vector256<byte> result = Avx2.AndNot(cmp, Vector256.Create((byte)1));
return result; return result;
} }
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
internal static Vector256<byte> CorrectBooleanInternal_Fallback(this Vector256<byte> vector)
{
Vector256<byte> output = IntrinsicUtility.GetUninitializedVector256<byte>(); Vector256<byte> output = IntrinsicUtility.GetUninitializedVector256<byte>();
for (var index = 0; index < Vector256<byte>.Count; index++) for (var index = 0; index < Vector256<byte>.Count; index++)
@ -175,17 +162,19 @@ public static class IntrinsicExtensions
} }
[Pure] [Pure]
[CLSCompliant(false)]
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
internal static Vector128<ulong> ReverseElementsInternal(this Vector128<ulong> vector, ISse2SupportProvider? supportProvider) internal static Vector256<byte> CorrectBooleanInternal_Avx2(this Vector256<byte> vector)
{ {
supportProvider ??= new SystemSse2SupportProvider(); Vector256<byte> cmp = Avx2.CompareEqual(vector, Vector256<byte>.Zero);
Vector256<byte> result = Avx2.AndNot(cmp, Vector256.Create((byte)1));
if (supportProvider.IsSupported) return result;
{
return Sse2.Shuffle(vector.AsDouble(), vector.AsDouble(), 0b01).AsUInt64();
} }
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
internal static Vector128<ulong> ReverseElementsInternal_Fallback(this Vector128<ulong> vector)
{
Vector128<ulong> output = IntrinsicUtility.GetUninitializedVector128<ulong>(); Vector128<ulong> output = IntrinsicUtility.GetUninitializedVector128<ulong>();
Unsafe.As<Vector128<ulong>, ulong>(ref output) = Unsafe.Add(ref Unsafe.As<Vector128<ulong>, ulong>(ref vector), 1); Unsafe.As<Vector128<ulong>, ulong>(ref output) = Unsafe.Add(ref Unsafe.As<Vector128<ulong>, ulong>(ref vector), 1);
@ -193,5 +182,12 @@ public static class IntrinsicExtensions
return output; return output;
} }
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
internal static Vector128<ulong> ReverseElementsInternal_Sse2(this Vector128<ulong> vector)
{
return Sse2.Shuffle(vector.AsDouble(), vector.AsDouble(), 0b01).AsUInt64();
}
} }
#endif #endif

View File

@ -1,13 +0,0 @@
namespace X10D;
/// <summary>
/// Represents an object which provides the status of the support of AVX2 instructions.
/// </summary>
public interface IAvx2SupportProvider
{
/// <summary>
/// Gets a value indicating whether AVX2 instructions are supported on this platform.
/// </summary>
/// <value><see langword="true" /> if AVX2 instructions are supported; otherwise, <see langword="false" />.</value>
bool IsSupported { get; }
}

View File

@ -1,13 +0,0 @@
namespace X10D;
/// <summary>
/// Represents an object which provides the status of the support of SSE2 instructions.
/// </summary>
public interface ISse2SupportProvider
{
/// <summary>
/// Gets a value indicating whether SSE2 instructions are supported on this platform.
/// </summary>
/// <value><see langword="true" /> if SSE2 instructions are supported; otherwise, <see langword="false" />.</value>
bool IsSupported { get; }
}

View File

@ -1,13 +0,0 @@
namespace X10D;
/// <summary>
/// Represents an object which provides the status of the support of SSSE3 instructions.
/// </summary>
public interface ISsse3SupportProvider
{
/// <summary>
/// Gets a value indicating whether SSSE3 instructions are supported on this platform.
/// </summary>
/// <value><see langword="true" /> if SSSE3 instructions are supported; otherwise, <see langword="false" />.</value>
bool IsSupported { get; }
}

View File

@ -1,20 +0,0 @@
using System.Diagnostics.CodeAnalysis;
#if NETCOREAPP3_0_OR_GREATER
using System.Runtime.Intrinsics.X86;
#endif
namespace X10D;
[ExcludeFromCodeCoverage]
internal struct SystemAvx2SupportProvider : IAvx2SupportProvider
{
/// <inheritdoc />
public bool IsSupported
{
#if NETCOREAPP3_0_OR_GREATER
get => Avx2.IsSupported;
#else
get => false;
#endif
}
}

View File

@ -1,20 +0,0 @@
using System.Diagnostics.CodeAnalysis;
#if NETCOREAPP3_0_OR_GREATER
using System.Runtime.Intrinsics.X86;
#endif
namespace X10D;
[ExcludeFromCodeCoverage]
internal struct SystemSse2SupportProvider : ISse2SupportProvider
{
/// <inheritdoc />
public bool IsSupported
{
#if NETCOREAPP3_0_OR_GREATER
get => Sse2.IsSupported;
#else
get => false;
#endif
}
}

View File

@ -1,20 +0,0 @@
using System.Diagnostics.CodeAnalysis;
#if NETCOREAPP3_0_OR_GREATER
using System.Runtime.Intrinsics.X86;
#endif
namespace X10D;
[ExcludeFromCodeCoverage]
internal struct SystemSsse3SupportProvider : ISsse3SupportProvider
{
/// <inheritdoc />
public bool IsSupported
{
#if NETCOREAPP3_0_OR_GREATER
get => Sse3.IsSupported;
#else
get => false;
#endif
}
}