diff --git a/Benchmark.csproj b/Benchmark.csproj index b1a51fc..0bbc819 100644 --- a/Benchmark.csproj +++ b/Benchmark.csproj @@ -7,6 +7,7 @@ + diff --git a/Identity.cs b/Identity.cs index 81e950b..9abebb4 100644 --- a/Identity.cs +++ b/Identity.cs @@ -1,4 +1,4 @@ -using System.Linq; +using System; using System.Runtime.CompilerServices; namespace Benchmark @@ -20,29 +20,74 @@ namespace Benchmark { identity = default; var position = value.Length; - if (!TryGetNumber(value, null, ref position, out var revision)) return false; + if (!TryGetNumberOld(ref value, null, ref position, out var revision)) return false; if (value[position] != '-') return false; - if (!TryGetNumber(value, 7, ref position, out var index)) return false; - if (!TryGetPrefix(value, ref position, out var prefix)) return false; + if (!TryGetNumberOld(ref value, 7, ref position, out var index)) return false; + if (!TryGetPrefixOld(ref value, ref position, out var prefix)) return false; + identity = new Identity(prefix, index, revision); + return true; } + + public static bool TryParse(Span value, out Identity identity) + { + identity = default; + var position = value.Length; + if (!TryGetNumber(ref value, null, ref position, out var revision)) return false; + if (value[position] != '-') return false; + if (!TryGetNumber(ref value, 7, ref position, out var index)) return false; + if (!TryGetPrefix(ref value, ref position, out var prefix)) return false; identity = new Identity(prefix, index, revision); return true; } - private static bool TryGetPrefix(in string identity, ref int lastPosition, out string prefix) + + private static bool TryGetPrefixOld(ref string identity, ref int lastPosition, out string prefix) { - prefix = identity.Remove(lastPosition + 1); + prefix = identity[(lastPosition + 1)..]; if (prefix.Length < 3) return false; - return !prefix.Any(character => character < 'A' || character > 'Z'); + for (int i = 0; i < prefix.Length; i++) + { + if (prefix[i] < 'A' || prefix[i] > 'Z') return false; + } + return true; } - private static bool TryGetNumber(in string identity, int? exactLength, ref int currentPosition, out int revision) + private static bool TryGetPrefix(ref Span identity, ref int lastPosition, out string prefix) + { + var span = identity[(lastPosition + 1)..]; + prefix = span.ToString(); + if (span.Length < 3) return false; + for (int i = 0; i < span.Length; i++) + { + if (span[i] < 'A' || span[i] > 'Z') return false; + } + + return true; + } + + private static bool TryGetNumberOld(ref string identity, int? exactLength, ref int currentPosition, out int revision) { revision = 0; var start = currentPosition; for (currentPosition = start - 1; currentPosition >= 0; currentPosition--) { var current = identity[currentPosition]; - if(current == '0') continue; + if (current == '0') continue; + if (current < '1' || current > '9') break; + revision += (current - '0') * IntegerPow(10, start - currentPosition - 1); + } + + if (exactLength != null && start - (currentPosition + 1) != exactLength) return false; + return revision != default; + } + + private static bool TryGetNumber(ref Span identity, int? exactLength, ref int currentPosition, out int revision) + { + revision = 0; + var start = currentPosition; + for (currentPosition = start - 1; currentPosition >= 0; currentPosition--) + { + var current = identity[currentPosition]; + if (current == '0') continue; if (current < '1' || current > '9') break; revision += (current - '0') * IntegerPow(10, start - currentPosition - 1); } diff --git a/IdentityParser.cs b/IdentityParser.cs index 9ae6a19..8fb0e2a 100644 --- a/IdentityParser.cs +++ b/IdentityParser.cs @@ -1,16 +1,29 @@ -using System.Text.RegularExpressions; +using System; +using System.Text.RegularExpressions; using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Jobs; namespace Benchmark { [MemoryDiagnoser] + //[SimpleJob(RuntimeMoniker.Net472, baseline: true)] + [SimpleJob(RuntimeMoniker.NetCoreApp31)] + [SimpleJob(RuntimeMoniker.NetCoreApp50)] public class IdentityParser { private const string TestIdentity = "ABZ0000001-1"; + private char[] TestIdentityChars; + private readonly Regex _regex = new Regex(@"^(\w{3,})(\d{7})-(\d+)$", RegexOptions.Compiled); + [GlobalSetup] + public void GlobalSetup() + { + TestIdentityChars = TestIdentity.ToCharArray(); + } + [Benchmark] - public Identity? Parse1() + public Identity? ParseWithRegex() { var match = _regex.Match(TestIdentity); if (!match.Success) return default; @@ -21,9 +34,15 @@ namespace Benchmark } [Benchmark] - public Identity? Parse2() + public Identity? ParseWithoutRegex() { return Identity.TryParse(TestIdentity, out var identity) ? identity : default(Identity?); } + + [Benchmark] + public Identity? ParseWithSpanT() + { + return Identity.TryParse(TestIdentityChars, out var identity) ? identity : default(Identity?); + } } } \ No newline at end of file diff --git a/TestProject1/TestProject1.csproj b/TestProject1/TestProject1.csproj index cb90b17..ee6543b 100644 --- a/TestProject1/TestProject1.csproj +++ b/TestProject1/TestProject1.csproj @@ -8,11 +8,11 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/TestProject1/UnitTest1.cs b/TestProject1/UnitTest1.cs index 3f6ea76..21b15f6 100644 --- a/TestProject1/UnitTest1.cs +++ b/TestProject1/UnitTest1.cs @@ -11,46 +11,46 @@ namespace TestProject1 } [Test] - public void Test3() + public void Test03() { var actual = new TrailingNumberFromString().GetTrailingNumberFromString3(); Assert.AreEqual(1234, actual); } [Test] - public void Test4() + public void Test04() { var actual = new TrailingNumberFromString().GetTrailingNumberFromString4(); Assert.AreEqual(1234, actual); } [Test] - public void Test5() + public void Test05() { var actual = new TrailingNumberFromString().GetTrailingNumberFromString5(); Assert.AreEqual(1234, actual); } [Test] - public void Test6() + public void Test06() { - var actual = new IdentityParser().Parse1(); + var actual = new IdentityParser().ParseWithoutRegex(); Assert.AreEqual("ABZ", actual?.Prefix); Assert.AreEqual(1, actual?.Index); Assert.AreEqual(1, actual?.Revision); } [Test] - public void Test7() + public void Test07() { - var actual = new IdentityParser().Parse2(); + var actual = new IdentityParser().ParseWithRegex(); Assert.AreEqual("ABZ", actual?.Prefix); Assert.AreEqual(1, actual?.Index); Assert.AreEqual(1, actual?.Revision); } [Test] - public void Test8() + public void Test08() { Assert.IsTrue(Identity.TryParse("TOPFTP0000123-12", out var actual)); Assert.AreEqual("TOPFTP", actual.Prefix); @@ -59,7 +59,7 @@ namespace TestProject1 } [Test] - public void Test9() + public void Test09() { Assert.IsTrue(Identity.TryParse("WPR1000023-12", out var actual)); Assert.AreEqual("WPR", actual.Prefix); @@ -77,6 +77,6 @@ namespace TestProject1 Assert.IsFalse(Identity.TryParse("NCL0000000-1", out _)); Assert.IsFalse(Identity.TryParse("Nc0000001-1", out _)); Assert.IsFalse(Identity.TryParse("0000001-1", out _)); - } + } } } \ No newline at end of file