From 46d2d4da58bb003f5ba79365946f498a25503cae Mon Sep 17 00:00:00 2001 From: Louis Zanella Date: Mon, 12 Aug 2024 16:50:38 -0400 Subject: [PATCH] Update dependencies and make use of modern idioms (#16) --- .github/dependabot.yml | 10 +- IntroToSpans/IntroToSpans.csproj | 10 +- IntroToSpans/Program.cs | 77 ++++++++------- SpanBenchmarks/Program.cs | 19 ++-- SpanBenchmarks/SpanBenchmarks.cs | 136 +++++++++++++-------------- SpanBenchmarks/SpanBenchmarks.csproj | 10 +- SpanBenchmarks/StringExtensions.cs | 15 ++- SpanBenchmarks/WordEnumerator.cs | 86 ++++++++--------- 8 files changed, 175 insertions(+), 188 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 24982ee..22dcf59 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,12 +1,8 @@ version: 2 updates: - package-ecosystem: nuget - directory: "/" + directory: '/' schedule: - interval: monthly - time: "10:00" + interval: weekly + time: '10:00' open-pull-requests-limit: 10 - # ignore: - # - dependency-name: Microsoft.CodeAnalysis.FxCopAnalyzers - # versions: - # - 3.3.2 diff --git a/IntroToSpans/IntroToSpans.csproj b/IntroToSpans/IntroToSpans.csproj index e43f359..a8c7863 100644 --- a/IntroToSpans/IntroToSpans.csproj +++ b/IntroToSpans/IntroToSpans.csproj @@ -1,18 +1,18 @@ - + Exe - net7.0 - 11.0 + net8.0 + 12.0 true - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/IntroToSpans/Program.cs b/IntroToSpans/Program.cs index 03237e2..c21c409 100644 --- a/IntroToSpans/Program.cs +++ b/IntroToSpans/Program.cs @@ -1,52 +1,51 @@ using System; using System.Runtime.InteropServices; -namespace IntroToSpans +namespace IntroToSpans; + +/// +/// Getting familiar with Span... +/// +/// +/// +internal class Program { - /// - /// Getting familiar with Span... - /// - /// - /// - internal class Program + private static void Main() { - private static void Main() - { - TestSpanWithAllMemoryTypes(); - } + TestSpanWithAllMemoryTypes(); + } + + private static void TestSpanWithAllMemoryTypes() + { + // managed memory + var byteArray = new byte[100]; + Span byteSpanOnManagedMemory = byteArray; - private static void TestSpanWithAllMemoryTypes() + // native memory + var nativeMemory = Marshal.AllocHGlobal(100); + Span byteSpanOnNativeMemory; + unsafe { - // managed memory - var byteArray = new byte[100]; - Span byteSpanOnManagedMemory = byteArray; - - // native memory - var nativeMemory = Marshal.AllocHGlobal(100); - Span byteSpanOnNativeMemory; - unsafe - { - byteSpanOnNativeMemory = new Span(nativeMemory.ToPointer(), 100); - } - - SafeSum(byteSpanOnNativeMemory); - Marshal.FreeHGlobal(nativeMemory); - - // stack memory - Span byteSpanOnStackMemory = stackalloc byte[100]; - SafeSum(byteSpanOnStackMemory); + byteSpanOnNativeMemory = new Span(nativeMemory.ToPointer(), 100); } - // this method does not care what kind of memory it works on - private static ulong SafeSum(Span bytes) - { - ulong sum = 0; - for (var i = 0; i < bytes.Length; i++) - { - sum += bytes[i]; - } + SafeSum(byteSpanOnNativeMemory); + Marshal.FreeHGlobal(nativeMemory); + + // stack memory + Span byteSpanOnStackMemory = stackalloc byte[100]; + SafeSum(byteSpanOnStackMemory); + } - return sum; + // this method does not care what kind of memory it works on + private static ulong SafeSum(Span bytes) + { + ulong sum = 0; + for (var i = 0; i < bytes.Length; i++) + { + sum += bytes[i]; } + + return sum; } } diff --git a/SpanBenchmarks/Program.cs b/SpanBenchmarks/Program.cs index 615edbb..f05d7f9 100644 --- a/SpanBenchmarks/Program.cs +++ b/SpanBenchmarks/Program.cs @@ -5,21 +5,20 @@ using BenchmarkDotNet.Running; #endif -namespace TestingSpan +namespace TestingSpan; + +internal static class Program { - internal static class Program + private static void Main() { - private static void Main() - { #if DEBUG - var bm = new SpanBenchmarks(); - var wordsFromSplit = bm.UseSplit(); - var wordsFromSpan = bm.UseSpan(); + var bm = new SpanBenchmarks(); + var wordsFromSplit = bm.UseSplit(); + var wordsFromSpan = bm.UseSpan(); - Console.WriteLine($"Sequences are equal: {wordsFromSpan.SequenceEqual(wordsFromSplit)}"); + Console.WriteLine($"Sequences are equal: {wordsFromSpan.SequenceEqual(wordsFromSplit)}"); #else - BenchmarkRunner.Run(); + BenchmarkRunner.Run(); #endif - } } } diff --git a/SpanBenchmarks/SpanBenchmarks.cs b/SpanBenchmarks/SpanBenchmarks.cs index 5cb424b..d9ea3c0 100644 --- a/SpanBenchmarks/SpanBenchmarks.cs +++ b/SpanBenchmarks/SpanBenchmarks.cs @@ -6,94 +6,94 @@ using BenchmarkDotNet.Attributes; using static TestingSpan.StringExtensions; -namespace TestingSpan +namespace TestingSpan; + +[MemoryDiagnoser] +public class SpanBenchmarks { - [MemoryDiagnoser] - public class SpanBenchmarks - { - private const string MyText = @" -Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna -aliqua. Turpis egestas sed tempus urna. Vel pharetra vel turpis nunc eget lorem dolor. A diam sollicitudin tempor id -eu nisl. Nunc scelerisque viverra mauris in aliquam. Massa eget egestas purus viverra accumsan in. Fermentum et -sollicitud in ac orci phasellus egestas. Purus ut faucibus pulvinar elementum integer enim neque volutpat ac. Adipiscing -tristique risus nec feugiat in fermentum. Ut faucibus pulvinar elementum integer enim neque volutpat. Ipsum dolor sit -amet consectetur adipiscing elit ut aliquam. Donec et odio pellentesque diam volutpat commodo sed egestas. Congue nisi -vitae suscipit tellus mauris. Mi in nulla posuere sollicitudin aliquam ultrices sagittis orci a. Quam nulla porttitor -massa id neque aliquam vestibulum. Nisl suscipit adipiscing bibendum est ultricies integer. Imperdiet sed euismod nisi -porta lorem. + private const string MyText = """ + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna + aliqua. Turpis egestas sed tempus urna. Vel pharetra vel turpis nunc eget lorem dolor. A diam sollicitudin tempor id + eu nisl. Nunc scelerisque viverra mauris in aliquam. Massa eget egestas purus viverra accumsan in. Fermentum et + sollicitud in ac orci phasellus egestas. Purus ut faucibus pulvinar elementum integer enim neque volutpat ac. Adipiscing + tristique risus nec feugiat in fermentum. Ut faucibus pulvinar elementum integer enim neque volutpat. Ipsum dolor sit + amet consectetur adipiscing elit ut aliquam. Donec et odio pellentesque diam volutpat commodo sed egestas. Congue nisi + vitae suscipit tellus mauris. Mi in nulla posuere sollicitudin aliquam ultrices sagittis orci a. Quam nulla porttitor + massa id neque aliquam vestibulum. Nisl suscipit adipiscing bibendum est ultricies integer. Imperdiet sed euismod nisi + porta lorem. -Viverra nibh cras pulvinar mattis. Lorem mollis aliquam ut porttitor. Tellus in hac habitasse platea dictumst vestibulum -rhoncus est pellentesque. Sed cras ornare arcu dui vivamus arcu felis. Blandit volutpat maecenas volutpat blandit -aliquam etiam erat velit. Eu augue ut lectus arcu bibendum at varius vel pharetra. Senectus et netus et malesuada fames -ac turpis. At volutpat diam ut venenatis. Adipiscing at in tellus integer feugiat scelerisque. Diam quis enim lobortis -scelerisque fermentum dui faucibus in. Nibh tellus molestie nunc non. Phasellus faucibus scelerisque eleifend donec. -Porta nibh venenatis cras sed. Consequat id porta nibh venenatis. Leo vel fringilla est ullamcorper eget. + Viverra nibh cras pulvinar mattis. Lorem mollis aliquam ut porttitor. Tellus in hac habitasse platea dictumst vestibulum + rhoncus est pellentesque. Sed cras ornare arcu dui vivamus arcu felis. Blandit volutpat maecenas volutpat blandit + aliquam etiam erat velit. Eu augue ut lectus arcu bibendum at varius vel pharetra. Senectus et netus et malesuada fames + ac turpis. At volutpat diam ut venenatis. Adipiscing at in tellus integer feugiat scelerisque. Diam quis enim lobortis + scelerisque fermentum dui faucibus in. Nibh tellus molestie nunc non. Phasellus faucibus scelerisque eleifend donec. + Porta nibh venenatis cras sed. Consequat id porta nibh venenatis. Leo vel fringilla est ullamcorper eget. -Cursus mattis molestie a iaculis at erat pellentesque adipiscing. Non enim praesent elementum facilisis leo vel -fringilla est ullamcorper. Augue interdum velit euismod in pellentesque massa placerat. Habitasse platea dictumst -quisque sagittis purus sit amet volutpat consequat. Volutpat est velit egestas dui id. Neque laoreet suspendisse -interdum consectetur. Nunc aliquet bibendum enim facilisis. Curabitur gravida arcu ac tortor dignissim convallis aenean -et tortor. Sagittis nisl rhoncus mattis rhoncus urna neque viverra justo. Amet nisl purus in mollis nunc sed id semper. -Enim blandit volutpat maecenas volutpat blandit aliquam. Mauris pharetra et ultrices neque ornare aenean. Et leo duis ut -diam quam. In nisl nisi scelerisque eu ultrices. Felis eget nunc lobortis mattis aliquam. Faucibus a pellentesque sit -amet porttitor eget dolor morbi. Sollicitudin ac orci phasellus egestas tellus rutrum tellus pellentesque eu. Odio ut -enim blandit volutpat maecenas volutpat blandit. Diam sollicitudin tempor id eu nisl nunc mi. + Cursus mattis molestie a iaculis at erat pellentesque adipiscing. Non enim praesent elementum facilisis leo vel + fringilla est ullamcorper. Augue interdum velit euismod in pellentesque massa placerat. Habitasse platea dictumst + quisque sagittis purus sit amet volutpat consequat. Volutpat est velit egestas dui id. Neque laoreet suspendisse + interdum consectetur. Nunc aliquet bibendum enim facilisis. Curabitur gravida arcu ac tortor dignissim convallis aenean + et tortor. Sagittis nisl rhoncus mattis rhoncus urna neque viverra justo. Amet nisl purus in mollis nunc sed id semper. + Enim blandit volutpat maecenas volutpat blandit aliquam. Mauris pharetra et ultrices neque ornare aenean. Et leo duis ut + diam quam. In nisl nisi scelerisque eu ultrices. Felis eget nunc lobortis mattis aliquam. Faucibus a pellentesque sit + amet porttitor eget dolor morbi. Sollicitudin ac orci phasellus egestas tellus rutrum tellus pellentesque eu. Odio ut + enim blandit volutpat maecenas volutpat blandit. Diam sollicitudin tempor id eu nisl nunc mi. -Praesent tristique magna sit amet purus. Cursus mattis molestie a iaculis. Lectus arcu bibendum at varius vel pharetra -vel turpis. Ultrices neque ornare aenean euismod elementum nisi quis. Morbi tristique senectus et netus et malesuada -fames. Magna eget est lorem ipsum dolor sit amet consectetur adipiscing. Hac habitasse platea dictumst quisque sagittis. -Nunc id cursus metus aliquam eleifend mi in nulla posuere. Ultrices dui sapien eget mi proin sed libero enim. Id neque -aliquam vestibulum morbi. Nunc id cursus metus aliquam eleifend mi. Dui ut ornare lectus sit. Suscipit tellus mauris a -diam maecenas sed enim ut. + Praesent tristique magna sit amet purus. Cursus mattis molestie a iaculis. Lectus arcu bibendum at varius vel pharetra + vel turpis. Ultrices neque ornare aenean euismod elementum nisi quis. Morbi tristique senectus et netus et malesuada + fames. Magna eget est lorem ipsum dolor sit amet consectetur adipiscing. Hac habitasse platea dictumst quisque sagittis. + Nunc id cursus metus aliquam eleifend mi in nulla posuere. Ultrices dui sapien eget mi proin sed libero enim. Id neque + aliquam vestibulum morbi. Nunc id cursus metus aliquam eleifend mi. Dui ut ornare lectus sit. Suscipit tellus mauris a + diam maecenas sed enim ut. -Nascetur ridiculus mus mauris vitae ultricies leo integer malesuada nunc. Quisque non tellus orci ac. Risus commodo -viverra maecenas accumsan lacus. Diam ut venenatis tellus in metus vulputate eu scelerisque. Elementum eu facilisis sed -odio. Vitae sapien pellentesque habitant morbi tristique senectus et netus et. Arcu felis bibendum ut tristique et -egestas quis. In ornare quam viverra orci sagittis eu volutpat odio facilisis. Nec tincidunt praesent semper feugiat -nibh sed pulvinar proin. Condimentum id venenatis a condimentum vitae sapien pellentesque habitant. Ac turpis egestas -maecenas pharetra convallis posuere morbi leo urna. Proin sagittis nisl rhoncus mattis rhoncus urna neque. Nisi lacus -sed viverra tellus. Euismod quis viverra nibh cras pulvinar mattis nunc sed blandit. Scelerisque viverra mauris in -aliquam sem. Habitant morbi tristique senectus et netus et malesuada. Consectetur adipiscing elit duis tristique -sollicitudin nibh sit amet commodo."; + Nascetur ridiculus mus mauris vitae ultricies leo integer malesuada nunc. Quisque non tellus orci ac. Risus commodo + viverra maecenas accumsan lacus. Diam ut venenatis tellus in metus vulputate eu scelerisque. Elementum eu facilisis sed + odio. Vitae sapien pellentesque habitant morbi tristique senectus et netus et. Arcu felis bibendum ut tristique et + egestas quis. In ornare quam viverra orci sagittis eu volutpat odio facilisis. Nec tincidunt praesent semper feugiat + nibh sed pulvinar proin. Condimentum id venenatis a condimentum vitae sapien pellentesque habitant. Ac turpis egestas + maecenas pharetra convallis posuere morbi leo urna. Proin sagittis nisl rhoncus mattis rhoncus urna neque. Nisi lacus + sed viverra tellus. Euismod quis viverra nibh cras pulvinar mattis nunc sed blandit. Scelerisque viverra mauris in + aliquam sem. Habitant morbi tristique senectus et netus et malesuada. Consectetur adipiscing elit duis tristique + sollicitudin nibh sit amet commodo. + """; - [Benchmark(Baseline = true)] - [SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "Benchmarks MUST be instance methods")] - public string[] UseSplit() - { + [Benchmark(Baseline = true)] + [SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "Benchmarks MUST be instance methods")] + public string[] UseSplit() + { #if DEBUG - var words = new List(); + var words = new List(); #endif - foreach (var word in MyText.Split(WordSeparators, StringSplitOptions.RemoveEmptyEntries)) - { + foreach (var word in MyText.Split(WordSeparators, StringSplitOptions.RemoveEmptyEntries)) + { #if DEBUG - words.Add(word); + words.Add(word); #endif - } + } #if DEBUG - return words.ToArray(); + return words.ToArray(); #else - return Array.Empty(); + return []; #endif - } + } - [Benchmark] - [SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "Benchmarks MUST be instance methods")] - public string[] UseSpan() - { + [Benchmark] + [SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "Benchmarks MUST be instance methods")] + public string[] UseSpan() + { #if DEBUG - var words = new List(); + var words = new List(); #endif - foreach (var word in MyText.SplitIntoWords()) - { + foreach (var word in MyText.SplitIntoWords()) + { #if DEBUG - words.Add(word.ToString()); + words.Add(word.ToString()); #endif - } + } #if DEBUG - return words.ToArray(); + return words.ToArray(); #else - return Array.Empty(); + return []; #endif - } } } diff --git a/SpanBenchmarks/SpanBenchmarks.csproj b/SpanBenchmarks/SpanBenchmarks.csproj index b1085f2..58e87c4 100644 --- a/SpanBenchmarks/SpanBenchmarks.csproj +++ b/SpanBenchmarks/SpanBenchmarks.csproj @@ -1,17 +1,17 @@ - + Exe - net7.0 - 11.0 + net8.0 + 12.0 true strict false - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/SpanBenchmarks/StringExtensions.cs b/SpanBenchmarks/StringExtensions.cs index 056f3b8..99e9383 100644 --- a/SpanBenchmarks/StringExtensions.cs +++ b/SpanBenchmarks/StringExtensions.cs @@ -1,14 +1,13 @@ using System; -namespace TestingSpan +namespace TestingSpan; + +public static class StringExtensions { - public static class StringExtensions - { - public static readonly char[] WordSeparators = new[] { ' ', '\r', '\n', '\t' }; + public static readonly char[] WordSeparators = [' ', '\r', '\n', '\t']; - public static WordEnumerator SplitIntoWords(this string str) - { - return new WordEnumerator(str.AsSpan()); // WordEnumerator is a struct -> no heap allocation here - } + public static WordEnumerator SplitIntoWords(this string str) + { + return new WordEnumerator(str.AsSpan()); // WordEnumerator is a struct -> no heap allocation here } } diff --git a/SpanBenchmarks/WordEnumerator.cs b/SpanBenchmarks/WordEnumerator.cs index 76af7c1..f4b9cc8 100644 --- a/SpanBenchmarks/WordEnumerator.cs +++ b/SpanBenchmarks/WordEnumerator.cs @@ -2,59 +2,53 @@ using System.Linq; using static TestingSpan.StringExtensions; -namespace TestingSpan +namespace TestingSpan; + +/// +/// Word Enumerator that can be used in a foreach statement +/// Must be a ref struct as it contains ReadOnlySpan field & property +/// +/// +/// Thanks to Meziantou, whose blog post inspired me: +/// https://www.meziantou.net/split-a-string-into-lines-without-allocation.htm +/// +public ref struct WordEnumerator(ReadOnlySpan strSpan) { - /// - /// Word Enumerator that can be used in a foreach statement - /// Must be a ref struct as it contains ReadOnlySpan field & property - /// - /// - /// Thanks to Meziantou, whose blog post inspired me: - /// https://www.meziantou.net/split-a-string-into-lines-without-allocation.htm - /// - public ref struct WordEnumerator + private ReadOnlySpan _remainingSpan = strSpan; + + // The following 2 members are needed for compatibility with the foreach operator + public ReadOnlySpan Current { get; private set; } = default; + public readonly WordEnumerator GetEnumerator() => this; + + public bool MoveNext() { - private ReadOnlySpan _remainingSpan; + var span = _remainingSpan; + if (span.Length == 0) + return false; - public WordEnumerator(ReadOnlySpan strSpan) + // Find the start of a separator series + var indexStart = span.IndexOfAny(WordSeparators); + if (indexStart == -1) // _remainingSpan contains a single word { - _remainingSpan = strSpan; - Current = default; + _remainingSpan = []; + Current = span; + return true; } - public ReadOnlySpan Current { get; private set; } // For compatibility with foreach operator - public WordEnumerator GetEnumerator() => this; // For compatibility with foreach operator - - public bool MoveNext() + // Find the end of the separator series + var indexEnd = indexStart; + while (indexEnd < span.Length - 1 && WordSeparators.Contains(span[indexEnd + 1])) { - var span = _remainingSpan; - if (span.Length == 0) - return false; - - // Find the start of a separator series - var indexStart = span.IndexOfAny(WordSeparators); - if (indexStart == -1) // _remainingSpan contains a single word - { - _remainingSpan = ReadOnlySpan.Empty; - Current = span; - return true; - } - - // Find the end of the separator series - var indexEnd = indexStart; - while (indexEnd < span.Length - 1 && WordSeparators.Contains(span[indexEnd + 1])) - { - indexEnd++; - } - - _remainingSpan = span[(indexEnd + 1)..]; - - // If this is an empty word, get the next one immediately - if (indexStart == 0) - return MoveNext(); - - Current = span[..indexStart]; - return true; + indexEnd++; } + + _remainingSpan = span[(indexEnd + 1)..]; + + // If this is an empty word, get the next one immediately + if (indexStart == 0) + return MoveNext(); + + Current = span[..indexStart]; + return true; } }