From 1e98e6910f82a9bd40418b51a27a91ec110f073e Mon Sep 17 00:00:00 2001 From: RealityProgrammer Date: Sun, 5 Mar 2023 23:57:11 +0700 Subject: [PATCH] Initial commit with SIMD implementation and some other specialization methods --- X10D/src/Collections/ArrayExtensions.cs | 10 +- X10D/src/Collections/BoolListExtensions.cs | 2 +- X10D/src/Collections/ByteExtensions.cs | 43 +++- X10D/src/Collections/DictionaryExtensions.cs | 252 ++++++++++++++++++- X10D/src/Collections/Int16Extensions.cs | 51 +++- X10D/src/Collections/Int32Extensions.cs | 92 ++++++- X10D/src/Collections/Int64Extensions.cs | 6 +- X10D/src/Core/RandomExtensions.cs | 2 + X10D/src/Drawing/ColorExtensions.cs | 1 + X10D/src/Reflection/MemberInfoExtensions.cs | 6 +- 10 files changed, 428 insertions(+), 37 deletions(-) diff --git a/X10D/src/Collections/ArrayExtensions.cs b/X10D/src/Collections/ArrayExtensions.cs index 816fabd..c6ed75b 100644 --- a/X10D/src/Collections/ArrayExtensions.cs +++ b/X10D/src/Collections/ArrayExtensions.cs @@ -58,14 +58,8 @@ public static class ArrayExtensions } #endif - int index = range.Start.Value; - int end = range.End.Value; - if (range.End.IsFromEnd) - { - end = array.Length - end; - } - - array.Clear(index, end - index); + (int offset, int length) = range.GetOffsetAndLength(array.Length); + array.Clear(offset, length); } /// diff --git a/X10D/src/Collections/BoolListExtensions.cs b/X10D/src/Collections/BoolListExtensions.cs index ee70050..65f31b5 100644 --- a/X10D/src/Collections/BoolListExtensions.cs +++ b/X10D/src/Collections/BoolListExtensions.cs @@ -26,7 +26,7 @@ public static class BoolListExtensions throw new ArgumentNullException(nameof(source)); } #endif - + if (source.Count > 8) { throw new ArgumentException("Source cannot contain more than than 8 elements.", nameof(source)); diff --git a/X10D/src/Collections/ByteExtensions.cs b/X10D/src/Collections/ByteExtensions.cs index be77738..2d82e1b 100644 --- a/X10D/src/Collections/ByteExtensions.cs +++ b/X10D/src/Collections/ByteExtensions.cs @@ -1,5 +1,10 @@ using System.Diagnostics.Contracts; +#if NETCOREAPP3_0_OR_GREATER +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +#endif + namespace X10D.Collections; /// @@ -35,9 +40,43 @@ public static class ByteExtensions throw new ArgumentException($"Destination must be at least {Size} in length.", nameof(destination)); } - for (var index = 0; index < Size; index++) +#if NETCOREAPP3_0_OR_GREATER + if (Ssse3.IsSupported) { - destination[index] = (value & (1 << index)) != 0; + Ssse3Implementation(value, destination); + return; + } +#endif + + FallbackImplementation(value, destination); + +#if NETCOREAPP3_0_OR_GREATER + unsafe static void Ssse3Implementation(byte value, Span destination) + { + fixed (bool* pDestination = destination) + { + var mask2 = Vector128.Create( + 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, + 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80 + ); + var mask1Lo = Vector128.Create((byte)0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1); + + var vec = Vector128.Create(value).AsByte(); + var shuffle = Ssse3.Shuffle(vec, mask1Lo); + var and = Sse2.AndNot(shuffle, mask2); + var cmp = Sse2.CompareEqual(and, Vector128.Zero); + var correctness = Sse2.And(cmp, Vector128.Create((byte)0x01)); + + Sse2.StoreScalar((long*)pDestination, correctness.AsInt64()); + } + } +#endif + static void FallbackImplementation(byte value, Span destination) + { + for (var index = 0; index < Size; index++) + { + destination[index] = (value & (1 << index)) != 0; + } } } } diff --git a/X10D/src/Collections/DictionaryExtensions.cs b/X10D/src/Collections/DictionaryExtensions.cs index 86ae231..29d241e 100644 --- a/X10D/src/Collections/DictionaryExtensions.cs +++ b/X10D/src/Collections/DictionaryExtensions.cs @@ -1,4 +1,6 @@ -using System.Diagnostics.Contracts; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Runtime.InteropServices; using System.Web; namespace X10D.Collections; @@ -8,6 +10,72 @@ namespace X10D.Collections; /// public static class DictionaryExtensions { + /// + /// Adds a key/value pair to the if the key does not already exist, or updates a + /// key/value pair in the by using the specified function if the key already + /// exists. + /// + /// The dictionary to update. + /// The key to be added or whose value should be updated. + /// The value to be added for an absent key. + /// + /// The function used to generate a new value for an existing key based on the key's existing value. + /// + /// The type of the keys in the dictionary. + /// The type of the values in the dictionary. + /// + /// The new value for the key. This will be either be (if the key was absent) or the result + /// of (if the key was present). + /// + /// + /// is . + /// -or- + /// is . + /// + public static TValue AddOrUpdate(this Dictionary dictionary, TKey key, TValue addValue, + Func updateValueFactory) + where TKey : notnull + { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(dictionary); +#else + if (dictionary is null) + { + throw new ArgumentNullException(nameof(dictionary)); + } +#endif +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(updateValueFactory); +#else + if (updateValueFactory is null) + { + throw new ArgumentNullException(nameof(updateValueFactory)); + } +#endif + +#if NET6_0_OR_GREATER + ref var value = ref CollectionsMarshal.GetValueRefOrAddDefault(dictionary, key, out bool exists); + if (exists) + { + value = updateValueFactory(key, value!); + } else + { + value = addValue; + } + + return value; +#else + if (dictionary.TryGetValue(key, out var old)) + { + dictionary[key] = updateValueFactory(key, old); + } + else + { + dictionary.Add(key, addValue); + } +#endif + } + /// /// Adds a key/value pair to the if the key does not already exist, or updates a /// key/value pair in the by using the specified function if the key already @@ -51,9 +119,9 @@ public static class DictionaryExtensions } #endif - if (dictionary.ContainsKey(key)) + if (dictionary.TryGetValue(key, out var old)) { - dictionary[key] = updateValueFactory(key, dictionary[key]); + dictionary[key] = updateValueFactory(key, old); } else { @@ -63,6 +131,85 @@ public static class DictionaryExtensions return dictionary[key]; } + /// + /// Uses the specified functions to add a key/value pair to the if the key does + /// not already exist, or to update a key/value pair in the if the key already + /// exists. + /// + /// The dictionary to update. + /// The key to be added or whose value should be updated. + /// The function used to generate a value for an absent key. + /// + /// The function used to generate a new value for an existing key based on the key's existing value. + /// + /// The type of the keys in the dictionary. + /// The type of the values in the dictionary. + /// + /// The new value for the key. This will be either be the result of (if the key was + /// absent) or the result of (if the key was present). + /// + /// + /// is . + /// -or- + /// is . + /// -or- + /// is . + /// + public static TValue AddOrUpdate(this Dictionary dictionary, TKey key, + Func addValueFactory, Func updateValueFactory) + where TKey : notnull + { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(dictionary); +#else + if (dictionary is null) + { + throw new ArgumentNullException(nameof(dictionary)); + } +#endif +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(addValueFactory); +#else + if (addValueFactory is null) + { + throw new ArgumentNullException(nameof(addValueFactory)); + } +#endif +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(updateValueFactory); +#else + if (updateValueFactory is null) + { + throw new ArgumentNullException(nameof(updateValueFactory)); + } +#endif + +#if NET6_0_OR_GREATER + ref var value = ref CollectionsMarshal.GetValueRefOrAddDefault(dictionary, key, out bool exists); + if (exists) + { + value = updateValueFactory(key, value!); + } + else + { + value = addValueFactory(key); + } + + return value; +#else + if (dictionary.TryGetValue(key, out var old)) + { + dictionary[key] = updateValueFactory(key, old); + } + else + { + dictionary.Add(key, addValueFactory(key)); + } + + return dictionary[key]; +#endif + } + /// /// Uses the specified functions to add a key/value pair to the if the key does /// not already exist, or to update a key/value pair in the if the key already @@ -116,9 +263,9 @@ public static class DictionaryExtensions } #endif - if (dictionary.ContainsKey(key)) + if (dictionary.TryGetValue(key, out var old)) { - dictionary[key] = updateValueFactory(key, dictionary[key]); + dictionary[key] = updateValueFactory(key, old); } else { @@ -128,6 +275,91 @@ public static class DictionaryExtensions return dictionary[key]; } + /// + /// Uses the specified functions and argument to add a key/value pair to the if + /// the key does not already exist, or to update a key/value pair in the if th + /// key already exists. + /// + /// The dictionary to update. + /// The key to be added or whose value should be updated. + /// The function used to generate a value for an absent key. + /// + /// The function used to generate a new value for an existing key based on the key's existing value. + /// + /// + /// An argument to pass into and . + /// + /// The type of the keys in the dictionary. + /// The type of the values in the dictionary. + /// + /// The type of an argument to pass into and . + /// + /// + /// The new value for the key. This will be either be the result of (if the key was + /// absent) or the result of (if the key was present). + /// + /// + /// is . + /// -or- + /// is . + /// -or- + /// is . + /// + public static TValue AddOrUpdate(this Dictionary dictionary, TKey key, + Func addValueFactory, Func updateValueFactory, TArg factoryArgument) + where TKey : notnull + { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(dictionary); +#else + if (dictionary is null) + { + throw new ArgumentNullException(nameof(dictionary)); + } +#endif +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(addValueFactory); +#else + if (addValueFactory is null) + { + throw new ArgumentNullException(nameof(addValueFactory)); + } +#endif +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(updateValueFactory); +#else + if (updateValueFactory is null) + { + throw new ArgumentNullException(nameof(updateValueFactory)); + } +#endif + +#if NET6_0_OR_GREATER + ref var value = ref CollectionsMarshal.GetValueRefOrAddDefault(dictionary, key, out bool exists); + if (exists) + { + value = updateValueFactory(key, value!, factoryArgument); + } + else + { + value = addValueFactory(key, factoryArgument); + } + + return value; +#else + if (dictionary.TryGetValue(key, out var old)) + { + dictionary[key] = updateValueFactory(key, old, factoryArgument); + } + else + { + dictionary.Add(key, addValueFactory(key, factoryArgument)); + } + + return dictionary[key]; +#endif + } + /// /// Uses the specified functions and argument to add a key/value pair to the if /// the key does not already exist, or to update a key/value pair in the if th @@ -187,9 +419,9 @@ public static class DictionaryExtensions } #endif - if (dictionary.ContainsKey(key)) + if (dictionary.TryGetValue(key, out var old)) { - dictionary[key] = updateValueFactory(key, dictionary[key], factoryArgument); + dictionary[key] = updateValueFactory(key, old, factoryArgument); } else { @@ -227,7 +459,7 @@ public static class DictionaryExtensions return string.Empty; } - return value.Contains(' ') ? $"\"{value}\"" : value; + return value.Contains(' ', StringComparison.InvariantCulture) ? $"\"{value}\"" : value; } static string GetQueryParameter(KeyValuePair pair) @@ -282,7 +514,7 @@ public static class DictionaryExtensions return string.Empty; } - return value.Contains(' ') ? $"\"{value}\"" : value; + return value.Contains(' ', StringComparison.InvariantCulture) ? $"\"{value}\"" : value; } string GetQueryParameter(KeyValuePair pair) @@ -351,7 +583,7 @@ public static class DictionaryExtensions return string.Empty; } - return value.Contains(' ') ? $"\"{value}\"" : value; + return value.Contains(' ', StringComparison.InvariantCulture) ? $"\"{value}\"" : value; } string GetQueryParameter(KeyValuePair pair) diff --git a/X10D/src/Collections/Int16Extensions.cs b/X10D/src/Collections/Int16Extensions.cs index 366cb12..5257c61 100644 --- a/X10D/src/Collections/Int16Extensions.cs +++ b/X10D/src/Collections/Int16Extensions.cs @@ -1,5 +1,10 @@ using System.Diagnostics.Contracts; +#if NETCOREAPP3_0_OR_GREATER +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +#endif + namespace X10D.Collections; /// @@ -17,9 +22,9 @@ public static class Int16Extensions [Pure] public static bool[] Unpack(this short value) { - Span buffer = stackalloc bool[Size]; - value.Unpack(buffer); - return buffer.ToArray(); + bool[] ret = new bool[Size]; + value.Unpack(ret); + return ret; } /// @@ -35,9 +40,45 @@ public static class Int16Extensions throw new ArgumentException($"Destination must be at least {Size} in length.", nameof(destination)); } - for (var index = 0; index < Size; index++) +#if NETCOREAPP3_0_OR_GREATER + if (Ssse3.IsSupported) { - destination[index] = (value & (1 << index)) != 0; + Ssse3Implementation(value, destination); + return; + } +#endif + + FallbackImplementation(value, destination); + +#if NETCOREAPP3_0_OR_GREATER + unsafe static void Ssse3Implementation(short value, Span destination) + { + fixed (bool* pDestination = destination) + { + var mask2 = Vector128.Create( + 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, + 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80 + ); + var mask1Lo = Vector128.Create((byte)0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1); + + var one = Vector128.Create((byte)0x01); + + var vec = Vector128.Create(value).AsByte(); + var shuffle = Ssse3.Shuffle(vec, mask1Lo); + var and = Sse2.AndNot(shuffle, mask2); + var cmp = Sse2.CompareEqual(and, Vector128.Zero); + var correctness = Sse2.And(cmp, one); + + Sse2.Store((byte*)pDestination, correctness); + } + } +#endif + static void FallbackImplementation(short value, Span destination) + { + for (var index = 0; index < Size; index++) + { + destination[index] = (value & (1 << index)) != 0; + } } } } diff --git a/X10D/src/Collections/Int32Extensions.cs b/X10D/src/Collections/Int32Extensions.cs index 82da040..6fd81b3 100644 --- a/X10D/src/Collections/Int32Extensions.cs +++ b/X10D/src/Collections/Int32Extensions.cs @@ -1,5 +1,10 @@ using System.Diagnostics.Contracts; +#if NETCOREAPP3_0_OR_GREATER +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +#endif + namespace X10D.Collections; /// @@ -17,9 +22,9 @@ public static class Int32Extensions [Pure] public static bool[] Unpack(this int value) { - Span buffer = stackalloc bool[Size]; - value.Unpack(buffer); - return buffer.ToArray(); + bool[] ret = new bool[Size]; + value.Unpack(ret); + return ret; } /// @@ -35,9 +40,86 @@ public static class Int32Extensions throw new ArgumentException($"Destination must be at least {Size} in length.", nameof(destination)); } - for (var index = 0; index < Size; index++) +#if NETCOREAPP3_0_OR_GREATER + // TODO: AdvSimd support. + + // https://stackoverflow.com/questions/24225786/fastest-way-to-unpack-32-bits-to-a-32-byte-simd-vector + if (Avx2.IsSupported) { - destination[index] = (value & (1 << index)) != 0; + Avx2Implementation(value, destination); + return; + } + if (Ssse3.IsSupported) + { + Ssse3Implementation(value, destination); + return; + } +#endif + + FallbackImplementation(value, destination); + +#if NETCOREAPP3_0_OR_GREATER + unsafe static void Avx2Implementation(int value, Span destination) + { + fixed (bool* pDestination = destination) + { + var mask1 = Vector256.Create( + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03 + ).AsByte(); + var mask2 = Vector256.Create( + 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, + 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, + 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, + 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80 + ); + + var vec = Vector256.Create(value).AsByte(); + var shuffle = Avx2.Shuffle(vec, mask1); + var and = Avx2.AndNot(shuffle, mask2); + var cmp = Avx2.CompareEqual(and, Vector256.Zero); + var correctness = Avx2.And(cmp, Vector256.Create((byte)0x01)); + + Avx.Store((byte*)pDestination, correctness); + } + } + unsafe static void Ssse3Implementation(int value, Span destination) + { + fixed (bool* pDestination = destination) + { + var mask2 = Vector128.Create( + 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, + 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80 + ); + var mask1Lo = Vector128.Create((byte)0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1); + var mask1Hi = Vector128.Create((byte)2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3); + + var one = Vector128.Create((byte)0x01); + + var vec = Vector128.Create(value).AsByte(); + var shuffle = Ssse3.Shuffle(vec, mask1Lo); + var and = Sse2.AndNot(shuffle, mask2); + var cmp = Sse2.CompareEqual(and, Vector128.Zero); + var correctness = Sse2.And(cmp, one); + + Sse2.Store((byte*)pDestination, correctness); + + shuffle = Ssse3.Shuffle(vec, mask1Hi); + and = Sse2.AndNot(shuffle, mask2); + cmp = Sse2.CompareEqual(and, Vector128.Zero); + correctness = Sse2.And(cmp, one); + Sse2.Store((byte*)pDestination + 16, correctness); + } + } +#endif + static void FallbackImplementation(int value, Span destination) + { + for (var index = 0; index < Size; index++) + { + destination[index] = (value & (1 << index)) != 0; + } } } } diff --git a/X10D/src/Collections/Int64Extensions.cs b/X10D/src/Collections/Int64Extensions.cs index d9c07ba..8a5b197 100644 --- a/X10D/src/Collections/Int64Extensions.cs +++ b/X10D/src/Collections/Int64Extensions.cs @@ -17,9 +17,9 @@ public static class Int64Extensions [Pure] public static bool[] Unpack(this long value) { - Span buffer = stackalloc bool[Size]; - value.Unpack(buffer); - return buffer.ToArray(); + bool[] ret = new bool[Size]; + value.Unpack(ret); + return ret; } /// diff --git a/X10D/src/Core/RandomExtensions.cs b/X10D/src/Core/RandomExtensions.cs index a111704..c99a5cc 100644 --- a/X10D/src/Core/RandomExtensions.cs +++ b/X10D/src/Core/RandomExtensions.cs @@ -9,7 +9,9 @@ namespace X10D.Core; /// public static class RandomExtensions { +#if !NET6_0_OR_GREATER private static readonly Random Shared = new(); +#endif /// /// Returns a random value that defined in a specified enum. diff --git a/X10D/src/Drawing/ColorExtensions.cs b/X10D/src/Drawing/ColorExtensions.cs index e21cf9e..bafafbf 100644 --- a/X10D/src/Drawing/ColorExtensions.cs +++ b/X10D/src/Drawing/ColorExtensions.cs @@ -1,6 +1,7 @@ using System.Diagnostics.Contracts; using System.Drawing; using System.Runtime.CompilerServices; +using X10D.Collections; namespace X10D.Drawing; diff --git a/X10D/src/Reflection/MemberInfoExtensions.cs b/X10D/src/Reflection/MemberInfoExtensions.cs index 5c9bc7e..ede419f 100644 --- a/X10D/src/Reflection/MemberInfoExtensions.cs +++ b/X10D/src/Reflection/MemberInfoExtensions.cs @@ -37,8 +37,8 @@ public static class MemberInfoExtensions throw new ArgumentNullException(nameof(member)); } #endif - - return member.HasCustomAttribute(typeof(T)); + + return Attribute.IsDefined(member, typeof(T)); } /// @@ -82,7 +82,7 @@ public static class MemberInfoExtensions attribute, typeof(Attribute)), nameof(attribute)); } - return member.GetCustomAttribute(attribute) is not null; + return Attribute.IsDefined(member, attribute); } ///