From fdc0c6aa352c3eeb3d0c85718ffbd8784b23607e Mon Sep 17 00:00:00 2001 From: Oliver Booth Date: Sun, 2 Apr 2023 15:17:28 +0100 Subject: [PATCH] perf: fix performance of intrisics This changes removes the CPU-arch support provider interfaces that were introduced with 87b6dbdd56386de457be27b579a249ce76e8c94c. 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. --- X10D.Tests/src/Collections/ByteTests.cs | 45 ++++++++++++---- X10D.Tests/src/Collections/Int16Tests.cs | 49 ++++++++++++++---- X10D.Tests/src/Collections/Int32Tests.cs | 65 +++++++++++++++++------- X10D.Tests/src/Core/IntrinsicTests.cs | 56 +++++++++----------- X10D/src/Collections/ByteExtensions.cs | 41 ++++++++------- X10D/src/Collections/Int16Extensions.cs | 53 ++++++++++--------- X10D/src/Collections/Int32Extensions.cs | 40 ++++++--------- X10D/src/Core/IntrinsicExtensions.cs | 62 +++++++++++----------- X10D/src/IAvx2SupportProvider.cs | 13 ----- X10D/src/ISse2SupportProvider.cs | 13 ----- X10D/src/ISsse3SupportProvider.cs | 13 ----- X10D/src/SystemAvx2SupportProvider.cs | 20 -------- X10D/src/SystemSse2SupportProvider.cs | 20 -------- X10D/src/SystemSsse3SupportProvider.cs | 20 -------- 14 files changed, 239 insertions(+), 271 deletions(-) delete mode 100644 X10D/src/IAvx2SupportProvider.cs delete mode 100644 X10D/src/ISse2SupportProvider.cs delete mode 100644 X10D/src/ISsse3SupportProvider.cs delete mode 100644 X10D/src/SystemAvx2SupportProvider.cs delete mode 100644 X10D/src/SystemSse2SupportProvider.cs delete mode 100644 X10D/src/SystemSsse3SupportProvider.cs diff --git a/X10D.Tests/src/Collections/ByteTests.cs b/X10D.Tests/src/Collections/ByteTests.cs index e2b59fdac..60300650a 100644 --- a/X10D.Tests/src/Collections/ByteTests.cs +++ b/X10D.Tests/src/Collections/ByteTests.cs @@ -1,5 +1,5 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; +using System.Runtime.Intrinsics.X86; +using Microsoft.VisualStudio.TestTools.UnitTesting; using X10D.Collections; namespace X10D.Tests.Collections; @@ -10,7 +10,8 @@ public class ByteTests [TestMethod] public void Unpack_ShouldUnpackToArrayCorrectly() { - bool[] bits = ((byte)0b11010100).Unpack(); + const byte value = 0b11010100; + bool[] bits = value.Unpack(); Assert.AreEqual(8, bits.Length); @@ -27,8 +28,9 @@ public void Unpack_ShouldUnpackToArrayCorrectly() [TestMethod] public void Unpack_ShouldUnpackToSpanCorrectly() { + const byte value = 0b11010100; Span bits = stackalloc bool[8]; - ((byte)0b11010100).Unpack(bits); + value.Unpack(bits); Assert.IsFalse(bits[0]); Assert.IsFalse(bits[1]); @@ -41,14 +43,35 @@ public void Unpack_ShouldUnpackToSpanCorrectly() } #if NET5_0_OR_GREATER + + [TestMethod] + public void UnpackInternal_Fallback_ShouldUnpackToSpanCorrectly() + { + const byte value = 0b11010100; + Span bits = stackalloc bool[8]; + 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 Unpack_ShouldUnpackToSpanCorrectly_GivenFallbackImplementation() + public void UnpackInternal_Ssse3_ShouldUnpackToSpanCorrectly() { - var mock = new Mock(); - mock.Setup(provider => provider.IsSupported).Returns(false); + if (!Sse3.IsSupported) + { + return; + } + const byte value = 0b11010100; Span bits = stackalloc bool[8]; - ((byte)0b11010100).UnpackInternal(bits, mock.Object); + value.UnpackInternal_Ssse3(bits); Assert.IsFalse(bits[0]); Assert.IsFalse(bits[1]); @@ -64,7 +87,8 @@ public void Unpack_ShouldUnpackToSpanCorrectly_GivenFallbackImplementation() [TestMethod] public void Unpack_ShouldRepackEqually() { - Assert.AreEqual(0b11010100, ((byte)0b11010100).Unpack().PackByte()); + const byte value = 0b11010100; + Assert.AreEqual(value, value.Unpack().PackByte()); } [TestMethod] @@ -72,8 +96,9 @@ public void Unpack_ShouldThrow_GivenTooSmallSpan() { Assert.ThrowsException(() => { + const byte value = 0b11010100; Span bits = stackalloc bool[0]; - ((byte)0b11010100).Unpack(bits); + value.Unpack(bits); }); } } diff --git a/X10D.Tests/src/Collections/Int16Tests.cs b/X10D.Tests/src/Collections/Int16Tests.cs index 17474f3ee..e5df97b48 100644 --- a/X10D.Tests/src/Collections/Int16Tests.cs +++ b/X10D.Tests/src/Collections/Int16Tests.cs @@ -1,5 +1,5 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; +using System.Runtime.Intrinsics.X86; +using Microsoft.VisualStudio.TestTools.UnitTesting; using X10D.Collections; namespace X10D.Tests.Collections; @@ -10,7 +10,8 @@ public class Int16Tests [TestMethod] public void Unpack_ShouldUnpackToArrayCorrectly() { - bool[] bits = ((short)0b11010100).Unpack(); + const short value = 0b11010100; + bool[] bits = value.Unpack(); Assert.AreEqual(16, bits.Length); @@ -32,8 +33,9 @@ public void Unpack_ShouldUnpackToArrayCorrectly() [TestMethod] public void Unpack_ShouldUnpackToSpanCorrectly() { + const short value = 0b11010100; Span bits = stackalloc bool[16]; - ((short)0b11010100).Unpack(bits); + value.Unpack(bits); Assert.IsFalse(bits[0]); Assert.IsFalse(bits[1]); @@ -50,15 +52,40 @@ public void Unpack_ShouldUnpackToSpanCorrectly() } } -#if NET5_0_OR_GREATER [TestMethod] public void Unpack_ShouldUnpackToSpanCorrectly_GivenFallbackImplementation() { - var mock = new Mock(); - mock.Setup(provider => provider.IsSupported).Returns(false); + const short value = 0b11010100; + Span bits = stackalloc bool[16]; + 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]); + + for (var index = 8; index < 16; index++) + { + Assert.IsFalse(bits[index]); + } + } + +#if NET5_0_OR_GREATER + [TestMethod] + public void UnpackInternal_Ssse3_ShouldUnpackToSpanCorrectly() + { + if (!Sse3.IsSupported) + { + return; + } + const short value = 0b11010100; Span bits = stackalloc bool[16]; - ((short)0b11010100).UnpackInternal(bits, mock.Object); + value.UnpackInternal_Ssse3(bits); Assert.IsFalse(bits[0]); Assert.IsFalse(bits[1]); @@ -79,7 +106,8 @@ public void Unpack_ShouldUnpackToSpanCorrectly_GivenFallbackImplementation() [TestMethod] public void Unpack_ShouldRepackEqually() { - Assert.AreEqual(0b11010100, ((short)0b11010100).Unpack().PackInt16()); + const short value = 0b11010100; + Assert.AreEqual(value, value.Unpack().PackInt16()); } [TestMethod] @@ -87,8 +115,9 @@ public void Unpack_ShouldThrow_GivenTooSmallSpan() { Assert.ThrowsException(() => { + const short value = 0b11010100; Span bits = stackalloc bool[0]; - ((short)0b11010100).Unpack(bits); + value.Unpack(bits); }); } } diff --git a/X10D.Tests/src/Collections/Int32Tests.cs b/X10D.Tests/src/Collections/Int32Tests.cs index f4c1fdf6a..c48e0a09e 100644 --- a/X10D.Tests/src/Collections/Int32Tests.cs +++ b/X10D.Tests/src/Collections/Int32Tests.cs @@ -1,5 +1,5 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; +using System.Runtime.Intrinsics.X86; +using Microsoft.VisualStudio.TestTools.UnitTesting; using X10D.Collections; namespace X10D.Tests.Collections; @@ -10,7 +10,8 @@ public class Int32Tests [TestMethod] public void Unpack_ShouldUnpackToArrayCorrectly() { - bool[] bits = 0b11010100.Unpack(); + const int value = 0b11010100; + bool[] bits = value.Unpack(); Assert.AreEqual(32, bits.Length); @@ -32,8 +33,31 @@ public void Unpack_ShouldUnpackToArrayCorrectly() [TestMethod] public void Unpack_ShouldUnpackToSpanCorrectly() { + const int value = 0b11010100; Span 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 bits = stackalloc bool[32]; + value.UnpackInternal_Fallback(bits); Assert.IsFalse(bits[0]); Assert.IsFalse(bits[1]); @@ -52,15 +76,16 @@ public void Unpack_ShouldUnpackToSpanCorrectly() #if NET5_0_OR_GREATER [TestMethod] - public void Unpack_ShouldUnpackToSpanCorrectly_GivenFallbackFromAvx2() + public void UnpackInternal_Ssse3_ShouldUnpackToSpanCorrectly() { - var ssse3Mock = new Mock(); - var avx2Mock = new Mock(); - avx2Mock.Setup(provider => provider.IsSupported).Returns(false); - ssse3Mock.Setup(provider => provider.IsSupported).Returns(true); + if (!Ssse3.IsSupported) + { + return; + } + const int value = 0b11010100; Span bits = stackalloc bool[32]; - 0b11010100.UnpackInternal(bits, ssse3Mock.Object, avx2Mock.Object); + value.UnpackInternal_Ssse3(bits); Assert.IsFalse(bits[0]); Assert.IsFalse(bits[1]); @@ -78,15 +103,16 @@ public void Unpack_ShouldUnpackToSpanCorrectly_GivenFallbackFromAvx2() } [TestMethod] - public void Unpack_ShouldUnpackToSpanCorrectly_GivenFallback() + public void UnpackInternal_Avx2_ShouldUnpackToSpanCorrectly() { - var ssse3Mock = new Mock(); - var avx2Mock = new Mock(); - ssse3Mock.Setup(provider => provider.IsSupported).Returns(false); - avx2Mock.Setup(provider => provider.IsSupported).Returns(false); + if (!Avx2.IsSupported) + { + return; + } + const int value = 0b11010100; Span bits = stackalloc bool[32]; - 0b11010100.UnpackInternal(bits, ssse3Mock.Object, avx2Mock.Object); + value.UnpackInternal_Avx2(bits); Assert.IsFalse(bits[0]); Assert.IsFalse(bits[1]); @@ -102,13 +128,13 @@ public void Unpack_ShouldUnpackToSpanCorrectly_GivenFallback() Assert.IsFalse(bits[index]); } } - #endif [TestMethod] public void Unpack_ShouldRepackEqually() { - Assert.AreEqual(0b11010100, 0b11010100.Unpack().PackInt32()); + const int value = 0b11010100; + Assert.AreEqual(value, value.Unpack().PackInt32()); } [TestMethod] @@ -116,8 +142,9 @@ public void Unpack_ShouldThrow_GivenTooSmallSpan() { Assert.ThrowsException(() => { + const int value = 0b11010100; Span bits = stackalloc bool[0]; - 0b11010100.Unpack(bits); + value.Unpack(bits); }); } } diff --git a/X10D.Tests/src/Core/IntrinsicTests.cs b/X10D.Tests/src/Core/IntrinsicTests.cs index 8e9e8aecb..df5ce0413 100644 --- a/X10D.Tests/src/Core/IntrinsicTests.cs +++ b/X10D.Tests/src/Core/IntrinsicTests.cs @@ -1,7 +1,7 @@ #if NET6_0_OR_GREATER using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; using X10D.Core; namespace X10D.Tests.Core; @@ -12,9 +12,6 @@ public class IntrinsicTests [TestMethod] public void CorrectBoolean_ShouldReturnExpectedVector64Result_GivenInputVector() { - var mock = new Mock(); - mock.Setup(provider => provider.IsSupported).Returns(true); - 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); @@ -24,89 +21,86 @@ public void CorrectBoolean_ShouldReturnExpectedVector64Result_GivenInputVector() } [TestMethod] - public void CorrectBoolean_ShouldReturnExpectedVector128Result_GivenInputVector() + public void CorrectBooleanInternal_Fallback_ShouldReturnExpectedVector128Result_GivenInputVector() { - var mock = new Mock(); - 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 expectedResult = Vector128.Create(0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, (byte)1); - Vector128 result = inputVector.CorrectBooleanInternal(mock.Object); + Vector128 result = inputVector.CorrectBooleanInternal_Fallback(); Assert.AreEqual(expectedResult, result); } [TestMethod] - public void CorrectBoolean_ShouldReturnExpectedVector128Result_WhenSse2NotSupported() + public void CorrectBooleanInternal_Sse2_ShouldReturnExpectedVector128Result_GivenInputVector() { - var mock = new Mock(); - mock.Setup(provider => provider.IsSupported).Returns(false); + if (!Sse2.IsSupported) + { + return; + } 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); - Vector128 result = inputVector.CorrectBooleanInternal(mock.Object); + Vector128 result = inputVector.CorrectBooleanInternal_Sse2(); Assert.AreEqual(expectedResult, result); } [TestMethod] - public void CorrectBoolean_ShouldReturnExpectedVector256Result_GivenInputVector() + public void CorrectBooleanInternal_Avx2_ShouldReturnExpectedVector256Result_GivenInputVector() { - var mock = new Mock(); - mock.Setup(provider => provider.IsSupported).Returns(true); + if (!Avx2.IsSupported) + { + 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, 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, 0, 0, 1, (byte)1); - Vector256 result = inputVector.CorrectBooleanInternal(mock.Object); + Vector256 result = inputVector.CorrectBooleanInternal_Avx2(); Assert.AreEqual(expectedResult, result); } [TestMethod] - public void CorrectBoolean_ShouldReturnExpectedVector256Result_WhenSse2NotSupported() + public void CorrectBooleanInternal_Fallback_ShouldReturnExpectedVector256Result_GivenInputVector() { - var mock = new Mock(); - 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, 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, 0, 0, 1, (byte)1); - Vector256 result = inputVector.CorrectBooleanInternal(mock.Object); + Vector256 result = inputVector.CorrectBooleanInternal_Fallback(); Assert.AreEqual(expectedResult, result); } [TestMethod] - public void ReverseElements_ShouldReturnExpectedVector128Result_GivenInputVector() + public void ReverseElementsInternal_Fallback_ShouldReturnExpectedVector128Result_GivenInputVector() { - var mock = new Mock(); - mock.Setup(provider => provider.IsSupported).Returns(true); - var inputVector = Vector128.Create(42UL, 69UL); var expectedResult = Vector128.Create(69UL, 42UL); - Vector128 result = inputVector.ReverseElementsInternal(mock.Object); + Vector128 result = inputVector.ReverseElementsInternal_Fallback(); Assert.AreEqual(expectedResult, result); } [TestMethod] - public void ReverseElements_ShouldReturnExpectedVector128Result_WhenSse2NotSupported() + public void ReverseElementsInternal_Sse2_ShouldReturnExpectedVector128Result_GivenInputVector() { - var mock = new Mock(); - mock.Setup(provider => provider.IsSupported).Returns(false); + if (!Sse2.IsSupported) + { + return; + } var inputVector = Vector128.Create(42UL, 69UL); var expectedResult = Vector128.Create(69UL, 42UL); - Vector128 result = inputVector.ReverseElementsInternal(mock.Object); + Vector128 result = inputVector.ReverseElementsInternal_Sse2(); Assert.AreEqual(expectedResult, result); } diff --git a/X10D/src/Collections/ByteExtensions.cs b/X10D/src/Collections/ByteExtensions.cs index 9094775da..d299cf4fb 100644 --- a/X10D/src/Collections/ByteExtensions.cs +++ b/X10D/src/Collections/ByteExtensions.cs @@ -1,5 +1,6 @@ using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; #if NETCOREAPP3_0_OR_GREATER using System.Runtime.Intrinsics; @@ -21,6 +22,11 @@ public static class ByteExtensions /// The value to unpack. /// An array of with length 8. [Pure] +#if NETCOREAPP3_1_OR_GREATER + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] +#else + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif public static bool[] Unpack(this byte value) { var buffer = new bool[Size]; @@ -35,20 +41,12 @@ public static bool[] Unpack(this byte value) /// When this method returns, contains the unpacked booleans from . /// is not large enough to contain the result. [ExcludeFromCodeCoverage] - public static void Unpack(this byte value, Span destination) - { -#if NETCOREAPP3_0_OR_GREATER - UnpackInternal(value, destination, new SystemSsse3SupportProvider()); +#if NETCOREAPP3_1_OR_GREATER + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] #else - UnpackInternal(value, destination); -#endif - } - -#if NETCOREAPP3_0_OR_GREATER - internal static void UnpackInternal(this byte value, Span destination, ISsse3SupportProvider? ssse3SupportProvider) -#else - internal static void UnpackInternal(this byte value, Span destination) + [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif + public static void Unpack(this byte value, Span destination) { if (destination.Length < Size) { @@ -56,9 +54,7 @@ internal static void UnpackInternal(this byte value, Span destination) } #if NETCOREAPP3_0_OR_GREATER - ssse3SupportProvider ??= new SystemSsse3SupportProvider(); - - if (ssse3SupportProvider.IsSupported) + if (Sse3.IsSupported) { UnpackInternal_Ssse3(value, destination); return; @@ -68,7 +64,12 @@ internal static void UnpackInternal(this byte value, Span destination) UnpackInternal_Fallback(value, destination); } - private static void UnpackInternal_Fallback(byte value, Span 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 destination) { for (var index = 0; index < Size; index++) { @@ -77,8 +78,12 @@ private static void UnpackInternal_Fallback(byte value, Span destination) } #if NETCOREAPP3_0_OR_GREATER - - private unsafe static void UnpackInternal_Ssse3(byte value, Span destination) +#if NETCOREAPP3_1_OR_GREATER + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] +#else + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + internal unsafe static void UnpackInternal_Ssse3(this byte value, Span destination) { fixed (bool* pDestination = destination) { diff --git a/X10D/src/Collections/Int16Extensions.cs b/X10D/src/Collections/Int16Extensions.cs index 46d2ce1cd..aaffabe31 100644 --- a/X10D/src/Collections/Int16Extensions.cs +++ b/X10D/src/Collections/Int16Extensions.cs @@ -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 using System.Runtime.Intrinsics; @@ -20,6 +22,11 @@ public static class Int16Extensions /// The value to unpack. /// An array of with length 16. [Pure] +#if NETCOREAPP3_1_OR_GREATER + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] +#else + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif public static bool[] Unpack(this short value) { var ret = new bool[Size]; @@ -33,20 +40,13 @@ public static bool[] Unpack(this short value) /// The value to unpack. /// When this method returns, contains the unpacked booleans from . /// is not large enough to contain the result. - public static void Unpack(this short value, Span destination) - { -#if NETCOREAPP3_0_OR_GREATER - UnpackInternal(value, destination, new SystemSsse3SupportProvider()); + [ExcludeFromCodeCoverage] +#if NETCOREAPP3_1_OR_GREATER + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] #else - UnpackInternal(value, destination); -#endif - } - -#if NETCOREAPP3_0_OR_GREATER - internal static void UnpackInternal(this short value, Span destination, ISsse3SupportProvider? ssse3SupportProvider) -#else - internal static void UnpackInternal(this short value, Span destination) + [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif + public static void Unpack(this short value, Span destination) { if (destination.Length < Size) { @@ -54,9 +54,7 @@ internal static void UnpackInternal(this short value, Span destination) } #if NETCOREAPP3_0_OR_GREATER - ssse3SupportProvider ??= new SystemSsse3SupportProvider(); - - if (ssse3SupportProvider.IsSupported) + if (Sse3.IsSupported) { UnpackInternal_Ssse3(value, destination); return; @@ -66,7 +64,12 @@ internal static void UnpackInternal(this short value, Span destination) UnpackInternal_Fallback(value, destination); } - private static void UnpackInternal_Fallback(short value, Span 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 destination) { for (var index = 0; index < Size; index++) { @@ -75,16 +78,12 @@ private static void UnpackInternal_Fallback(short value, Span destination) } #if NETCOREAPP3_0_OR_GREATER - private struct SystemSsse3SupportProvider : ISsse3SupportProvider - { - /// - public bool IsSupported - { - get => Sse3.IsSupported; - } - } - - private unsafe static void UnpackInternal_Ssse3(short value, Span destination) +#if NETCOREAPP3_1_OR_GREATER + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] +#else + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + internal unsafe static void UnpackInternal_Ssse3(this short value, Span destination) { fixed (bool* pDestination = destination) { diff --git a/X10D/src/Collections/Int32Extensions.cs b/X10D/src/Collections/Int32Extensions.cs index 38439e384..f718c739b 100644 --- a/X10D/src/Collections/Int32Extensions.cs +++ b/X10D/src/Collections/Int32Extensions.cs @@ -1,5 +1,6 @@ using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; #if NETCOREAPP3_0_OR_GREATER using System.Runtime.Intrinsics; @@ -36,22 +37,6 @@ public static bool[] Unpack(this int value) /// is not large enough to contain the result. [ExcludeFromCodeCoverage] public static void Unpack(this int value, Span 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 destination -#if NETCOREAPP3_0_OR_GREATER - , - ISsse3SupportProvider? ssse3SupportProvider, - IAvx2SupportProvider? avx2SupportProvider -#endif - ) { if (destination.Length < Size) { @@ -59,16 +44,13 @@ Span destination } #if NETCOREAPP3_0_OR_GREATER - ssse3SupportProvider ??= new SystemSsse3SupportProvider(); - avx2SupportProvider ??= new SystemAvx2SupportProvider(); - - if (avx2SupportProvider.IsSupported) + if (Avx2.IsSupported) { UnpackInternal_Avx2(value, destination); return; } - if (ssse3SupportProvider.IsSupported) + if (Sse3.IsSupported) { UnpackInternal_Ssse3(value, destination); return; @@ -78,7 +60,12 @@ Span destination UnpackInternal_Fallback(value, destination); } - private static void UnpackInternal_Fallback(int value, Span 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 destination) { for (var index = 0; index < Size; index++) { @@ -87,7 +74,12 @@ private static void UnpackInternal_Fallback(int value, Span destination) } #if NETCOREAPP3_0_OR_GREATER - private static unsafe void UnpackInternal_Ssse3(int value, Span 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 destination) { fixed (bool* pDestination = destination) { @@ -117,7 +109,7 @@ private static unsafe void UnpackInternal_Ssse3(int value, Span destinatio } } - private static unsafe void UnpackInternal_Avx2(int value, Span destination) + internal static unsafe void UnpackInternal_Avx2(this int value, Span destination) { fixed (bool* pDestination = destination) { diff --git a/X10D/src/Core/IntrinsicExtensions.cs b/X10D/src/Core/IntrinsicExtensions.cs index 11832b7bd..8c8517257 100644 --- a/X10D/src/Core/IntrinsicExtensions.cs +++ b/X10D/src/Core/IntrinsicExtensions.cs @@ -71,7 +71,7 @@ public static Vector64 CorrectBoolean(this Vector64 vector) [ExcludeFromCodeCoverage] public static Vector128 CorrectBoolean(this Vector128 vector) { - return CorrectBooleanInternal(vector, new SystemSse2SupportProvider()); + return Sse2.IsSupported ? CorrectBooleanInternal_Sse2(vector) : CorrectBooleanInternal_Fallback(vector); } /// @@ -94,7 +94,7 @@ public static Vector128 CorrectBoolean(this Vector128 vector) [ExcludeFromCodeCoverage] public static Vector256 CorrectBoolean(this Vector256 vector) { - return CorrectBooleanInternal(vector, new SystemAvx2SupportProvider()); + return Avx2.IsSupported ? CorrectBooleanInternal_Avx2(vector) : CorrectBooleanInternal_Fallback(vector); } /// @@ -118,26 +118,13 @@ public static Vector256 CorrectBoolean(this Vector256 vector) [ExcludeFromCodeCoverage] public static Vector128 ReverseElements(this Vector128 vector) { - return ReverseElementsInternal(vector, new SystemSse2SupportProvider()); + return Sse2.IsSupported ? ReverseElementsInternal_Sse2(vector) : ReverseElementsInternal_Fallback(vector); } [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - internal static Vector128 CorrectBooleanInternal(this Vector128 vector, ISse2SupportProvider? sse2SupportProvider) + internal static Vector128 CorrectBooleanInternal_Fallback(this Vector128 vector) { - sse2SupportProvider ??= new SystemSse2SupportProvider(); - - if (sse2SupportProvider.IsSupported) - { - Vector128 cmp = Sse2.CompareEqual(vector, Vector128.Zero); - Vector128 result = Sse2.AndNot(cmp, Vector128.Create((byte)1)); - - return result; - } - - // TODO: AdvSimd implementation. - // TODO: WasmSimd implementation. - Vector128 output = IntrinsicUtility.GetUninitializedVector128(); for (var index = 0; index < Vector128.Count; index++) @@ -151,18 +138,18 @@ internal static Vector128 CorrectBooleanInternal(this Vector128 vect [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - internal static Vector256 CorrectBooleanInternal(this Vector256 vector, IAvx2SupportProvider? supportProvider) + internal static Vector128 CorrectBooleanInternal_Sse2(this Vector128 vector) { - supportProvider ??= new SystemAvx2SupportProvider(); + Vector128 cmp = Sse2.CompareEqual(vector, Vector128.Zero); + Vector128 result = Sse2.AndNot(cmp, Vector128.Create((byte)1)); - if (supportProvider.IsSupported) - { - Vector256 cmp = Avx2.CompareEqual(vector, Vector256.Zero); - Vector256 result = Avx2.AndNot(cmp, Vector256.Create((byte)1)); - - return result; - } + return result; + } + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + internal static Vector256 CorrectBooleanInternal_Fallback(this Vector256 vector) + { Vector256 output = IntrinsicUtility.GetUninitializedVector256(); for (var index = 0; index < Vector256.Count; index++) @@ -175,17 +162,19 @@ internal static Vector256 CorrectBooleanInternal(this Vector256 vect } [Pure] - [CLSCompliant(false)] [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - internal static Vector128 ReverseElementsInternal(this Vector128 vector, ISse2SupportProvider? supportProvider) + internal static Vector256 CorrectBooleanInternal_Avx2(this Vector256 vector) { - supportProvider ??= new SystemSse2SupportProvider(); + Vector256 cmp = Avx2.CompareEqual(vector, Vector256.Zero); + Vector256 result = Avx2.AndNot(cmp, Vector256.Create((byte)1)); - if (supportProvider.IsSupported) - { - return Sse2.Shuffle(vector.AsDouble(), vector.AsDouble(), 0b01).AsUInt64(); - } + return result; + } + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + internal static Vector128 ReverseElementsInternal_Fallback(this Vector128 vector) + { Vector128 output = IntrinsicUtility.GetUninitializedVector128(); Unsafe.As, ulong>(ref output) = Unsafe.Add(ref Unsafe.As, ulong>(ref vector), 1); @@ -193,5 +182,12 @@ internal static Vector128 ReverseElementsInternal(this Vector128 v return output; } + + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + internal static Vector128 ReverseElementsInternal_Sse2(this Vector128 vector) + { + return Sse2.Shuffle(vector.AsDouble(), vector.AsDouble(), 0b01).AsUInt64(); + } } #endif diff --git a/X10D/src/IAvx2SupportProvider.cs b/X10D/src/IAvx2SupportProvider.cs deleted file mode 100644 index 093ae297c..000000000 --- a/X10D/src/IAvx2SupportProvider.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace X10D; - -/// -/// Represents an object which provides the status of the support of AVX2 instructions. -/// -public interface IAvx2SupportProvider -{ - /// - /// Gets a value indicating whether AVX2 instructions are supported on this platform. - /// - /// if AVX2 instructions are supported; otherwise, . - bool IsSupported { get; } -} diff --git a/X10D/src/ISse2SupportProvider.cs b/X10D/src/ISse2SupportProvider.cs deleted file mode 100644 index 1af720f19..000000000 --- a/X10D/src/ISse2SupportProvider.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace X10D; - -/// -/// Represents an object which provides the status of the support of SSE2 instructions. -/// -public interface ISse2SupportProvider -{ - /// - /// Gets a value indicating whether SSE2 instructions are supported on this platform. - /// - /// if SSE2 instructions are supported; otherwise, . - bool IsSupported { get; } -} diff --git a/X10D/src/ISsse3SupportProvider.cs b/X10D/src/ISsse3SupportProvider.cs deleted file mode 100644 index 1ed50d310..000000000 --- a/X10D/src/ISsse3SupportProvider.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace X10D; - -/// -/// Represents an object which provides the status of the support of SSSE3 instructions. -/// -public interface ISsse3SupportProvider -{ - /// - /// Gets a value indicating whether SSSE3 instructions are supported on this platform. - /// - /// if SSSE3 instructions are supported; otherwise, . - bool IsSupported { get; } -} diff --git a/X10D/src/SystemAvx2SupportProvider.cs b/X10D/src/SystemAvx2SupportProvider.cs deleted file mode 100644 index d15dbce5b..000000000 --- a/X10D/src/SystemAvx2SupportProvider.cs +++ /dev/null @@ -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 -{ - /// - public bool IsSupported - { -#if NETCOREAPP3_0_OR_GREATER - get => Avx2.IsSupported; -#else - get => false; -#endif - } -} diff --git a/X10D/src/SystemSse2SupportProvider.cs b/X10D/src/SystemSse2SupportProvider.cs deleted file mode 100644 index 7b3464238..000000000 --- a/X10D/src/SystemSse2SupportProvider.cs +++ /dev/null @@ -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 -{ - /// - public bool IsSupported - { -#if NETCOREAPP3_0_OR_GREATER - get => Sse2.IsSupported; -#else - get => false; -#endif - } -} diff --git a/X10D/src/SystemSsse3SupportProvider.cs b/X10D/src/SystemSsse3SupportProvider.cs deleted file mode 100644 index 4d127282c..000000000 --- a/X10D/src/SystemSsse3SupportProvider.cs +++ /dev/null @@ -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 -{ - /// - public bool IsSupported - { -#if NETCOREAPP3_0_OR_GREATER - get => Sse3.IsSupported; -#else - get => false; -#endif - } -}