Last active
September 11, 2019 06:47
-
-
Save loic-sharma/c10bc598fe83dbb15fcff3d0fa64a423 to your computer and use it in GitHub Desktop.
Improving NuGetVersion parse performance
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using BenchmarkDotNet.Attributes; | |
using BenchmarkDotNet.Running; | |
namespace ConsoleApp1 | |
{ | |
class Program | |
{ | |
static void Main(string[] args) | |
{ | |
BenchmarkRunner.Run<VersionParsing>(); | |
} | |
} | |
[MemoryDiagnoser] | |
public class VersionParsing | |
{ | |
//public const string VersionInput = "1.0.0"; | |
public const string VersionInput = "5.3.0-rtm.6192+bb60d6720d24890b8f3e071e70d27ea0f2bef57e"; | |
[Benchmark] | |
public Tuple<string, string[], string> Baseline() => ParseSectionsBaseline(VersionInput); | |
[Benchmark] | |
public Tuple<string, string[], string> BetterLoop() => ParseSectionsWithBetterLoop(VersionInput); | |
[Benchmark] | |
public ParseResult Span() => ParseSectionsWithSpan(VersionInput.AsSpan()); | |
[Benchmark] | |
public Tuple<string, string[], string> IndexOf() => ParseSectionsWithIndexOf(VersionInput); | |
[Benchmark] | |
public ParseResult SpanAndIndexOf() => ParseSectionsWithSpanAndIndexOf(VersionInput.AsSpan()); | |
internal static Tuple<string, string[], string> ParseSectionsBaseline(string value) | |
{ | |
string versionString = null; | |
string[] releaseLabels = null; | |
string buildMetadata = null; | |
var dashPos = -1; | |
var plusPos = -1; | |
var end = false; | |
for (var i = 0; i < value.Length; i++) | |
{ | |
end = (i == value.Length - 1); | |
if (dashPos < 0) | |
{ | |
if (end | |
|| value[i] == '-' | |
|| value[i] == '+') | |
{ | |
var endPos = i + (end ? 1 : 0); | |
versionString = value.Substring(0, endPos); | |
dashPos = i; | |
if (value[i] == '+') | |
{ | |
plusPos = i; | |
} | |
} | |
} | |
else if (plusPos < 0) | |
{ | |
if (end || value[i] == '+') | |
{ | |
var start = dashPos + 1; | |
var endPos = i + (end ? 1 : 0); | |
var releaseLabel = value.Substring(start, endPos - start); | |
releaseLabels = releaseLabel.Split('.'); | |
plusPos = i; | |
} | |
} | |
else if (end) | |
{ | |
var start = plusPos + 1; | |
var endPos = i + (end ? 1 : 0); | |
buildMetadata = value.Substring(start, endPos - start); | |
} | |
} | |
return new Tuple<string, string[], string>(versionString, releaseLabels, buildMetadata); | |
} | |
internal static Tuple<string, string[], string> ParseSectionsWithBetterLoop(string value) | |
{ | |
string versionString = null; | |
string[] releaseLabels = null; | |
string buildMetadata = null; | |
var state = ParseState.ParseVersion; | |
var segmentStart = 0; | |
for (var i = 0; i < value.Length; i++) | |
{ | |
if (value[i] == '-' || value[i] == '+') | |
{ | |
versionString = value.Substring(0, i); | |
segmentStart = i + 1; | |
state = value[i] == '-' | |
? ParseState.ParseReleaseLabel | |
: ParseState.ParseBuildMetadata; | |
break; | |
} | |
} | |
if (state == ParseState.ParseReleaseLabel) | |
{ | |
for (var i = segmentStart; i < value.Length; i++) | |
{ | |
if (value[i] == '+') | |
{ | |
releaseLabels = value.Substring(segmentStart, i - segmentStart).Split('.'); | |
segmentStart = i + 1; | |
state = ParseState.ParseBuildMetadata; | |
break; | |
} | |
} | |
} | |
if (segmentStart < value.Length) | |
{ | |
switch (state) | |
{ | |
case ParseState.ParseVersion: | |
versionString = value; | |
break; | |
case ParseState.ParseReleaseLabel: | |
releaseLabels = value.Substring(segmentStart, value.Length - segmentStart).Split('.'); | |
break; | |
case ParseState.ParseBuildMetadata: | |
buildMetadata = value.Substring(segmentStart, value.Length - segmentStart); | |
break; | |
} | |
} | |
return new Tuple<string, string[], string>(versionString, releaseLabels, buildMetadata); | |
} | |
internal static ParseResult ParseSectionsWithSpan(ReadOnlySpan<char> value) | |
{ | |
var result = new ParseResult(); | |
var dashPos = -1; | |
var plusPos = -1; | |
var end = false; | |
for (var i = 0; i < value.Length; i++) | |
{ | |
end = (i == value.Length - 1); | |
if (dashPos < 0) | |
{ | |
if (end | |
|| value[i] == '-' | |
|| value[i] == '+') | |
{ | |
var endPos = i + (end ? 1 : 0); | |
result.Version = value.Slice(0, endPos); | |
dashPos = i; | |
if (value[i] == '+') | |
{ | |
plusPos = i; | |
} | |
} | |
} | |
else if (plusPos < 0) | |
{ | |
if (end || value[i] == '+') | |
{ | |
var start = dashPos + 1; | |
var endPos = i + (end ? 1 : 0); | |
result.ReleaseLabel = value.Slice(start, endPos - start); | |
plusPos = i; | |
} | |
} | |
else if (end) | |
{ | |
var start = plusPos + 1; | |
var endPos = i + (end ? 1 : 0); | |
result.BuildMetadata = value.Slice(start, endPos - start); | |
} | |
} | |
return result; | |
} | |
internal static Tuple<string, string[], string> ParseSectionsWithIndexOf(string value) | |
{ | |
var dashPos = value.IndexOf('-'); | |
var plusPos = value.IndexOf('+'); | |
// If there is no dash or plus, the entire value is the version string. | |
if (dashPos == -1 && plusPos == -1) | |
{ | |
return new Tuple<string, string[], string>(value, Array.Empty<string>(), string.Empty); | |
} | |
// At this point, there is either a dash or a plus. | |
var releaseLabelStart = dashPos + 1; | |
var buildMetadataStart = plusPos + 1; | |
if (dashPos == -1) | |
{ | |
// The string contains a version and build metadata, but no release label. | |
return new Tuple<string, string[], string>( | |
value.Substring(0, plusPos), | |
Array.Empty<string>(), | |
value.Substring(buildMetadataStart, value.Length - buildMetadataStart)); | |
} | |
// At this point, there is a dash there may be a plus. | |
if (plusPos == -1) | |
{ | |
// The string contains a version and a release label, but no build metadata. | |
return new Tuple<string, string[], string>( | |
value.Substring(0, dashPos), | |
value.Substring(releaseLabelStart, value.Length - releaseLabelStart).Split('.'), | |
string.Empty); | |
} | |
// The string contains a version, a release label, and build metadata. | |
return new Tuple<string, string[], string>( | |
value.Substring(0, dashPos), | |
value.Substring(releaseLabelStart, value.Length - releaseLabelStart).Split('.'), | |
value.Substring(buildMetadataStart, value.Length - buildMetadataStart)); | |
} | |
internal static ParseResult ParseSectionsWithSpanAndIndexOf(ReadOnlySpan<char> value) | |
{ | |
var result = new ParseResult(); | |
var dashPos = value.IndexOf('-'); | |
var plusPos = value.IndexOf('+'); | |
// If there is no dash or plus, the entire value is the version string. | |
if (dashPos == -1 && plusPos == -1) | |
{ | |
result.Version = value; | |
result.ReleaseLabel = Span<char>.Empty; | |
result.BuildMetadata = Span<char>.Empty; | |
return result; | |
} | |
// At this point, there is either a dash or a plus. | |
var releaseLabelStart = dashPos + 1; | |
var buildMetadataStart = plusPos + 1; | |
if (dashPos == -1) | |
{ | |
// The string contains a version and build metadata, but no release label. | |
result.Version = value.Slice(0, plusPos); | |
result.ReleaseLabel = Span<char>.Empty; | |
result.BuildMetadata = value.Slice(buildMetadataStart, value.Length - buildMetadataStart); | |
return result; | |
} | |
// At this point, there is a dash there may be a plus. | |
if (plusPos == -1) | |
{ | |
// The string contains a version and a release label, but no build metadata. | |
result.Version = value.Slice(0, dashPos); | |
result.ReleaseLabel = value.Slice(releaseLabelStart, value.Length - releaseLabelStart); | |
result.BuildMetadata = Span<char>.Empty; | |
return result; | |
} | |
// The string contains a version, a release label, and build metadata. | |
result.Version = value.Slice(0, dashPos); | |
result.ReleaseLabel = value.Slice(releaseLabelStart, value.Length - releaseLabelStart); | |
result.BuildMetadata = value.Slice(buildMetadataStart, value.Length - buildMetadataStart); | |
return result; | |
} | |
public enum ParseState | |
{ | |
ParseVersion, | |
ParseReleaseLabel, | |
ParseBuildMetadata | |
} | |
public ref struct ParseResult | |
{ | |
public ReadOnlySpan<char> Version { get; set; } | |
public ReadOnlySpan<char> ReleaseLabel { get; set; } | |
public ReadOnlySpan<char> BuildMetadata { get; set; } | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
BenchmarkDotNet=v0.11.5, OS=Windows 10.0.18362 | |
Intel Core i5-6600K CPU 3.50GHz (Skylake), 1 CPU, 4 logical and 4 physical cores | |
.NET Core SDK=2.2.401 | |
[Host] : .NET Core 2.2.6 (CoreCLR 4.6.27817.03, CoreFX 4.6.27818.02), 64bit RyuJIT [AttachedDebugger] | |
DefaultJob : .NET Core 2.2.6 (CoreCLR 4.6.27817.03, CoreFX 4.6.27818.02), 64bit RyuJIT | |
| Method | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated | | |
|--------------- |-----------:|----------:|----------:|-------:|------:|------:|----------:| | |
| Baseline | 17.6074 ns | 0.3885 ns | 0.3444 ns | 0.0127 | - | - | 40 B | | |
| BetterLoop | 14.6490 ns | 0.1953 ns | 0.1827 ns | 0.0127 | - | - | 40 B | | |
| Span | 23.5565 ns | 0.1720 ns | 0.1609 ns | - | - | - | - | | |
| IndexOf | 18.9405 ns | 0.2117 ns | 0.1877 ns | 0.0127 | - | - | 40 B | | |
| SpanAndIndexOf | 27.7384 ns | 0.2773 ns | 0.2594 ns | - | - | - | - | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
BenchmarkDotNet=v0.11.5, OS=Windows 10.0.18362 | |
Intel Core i5-6600K CPU 3.50GHz (Skylake), 1 CPU, 4 logical and 4 physical cores | |
.NET Core SDK=2.2.401 | |
[Host] : .NET Core 2.2.6 (CoreCLR 4.6.27817.03, CoreFX 4.6.27818.02), 64bit RyuJIT [AttachedDebugger] | |
DefaultJob : .NET Core 2.2.6 (CoreCLR 4.6.27817.03, CoreFX 4.6.27818.02), 64bit RyuJIT | |
| Method | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated | | |
|--------------- |------------:|----------:|----------:|-------:|------:|------:|----------:| | |
| Baseline | 185.6161 ns | 1.6814 ns | 1.5727 ns | 0.1118 | - | - | 352 B | | |
| BetterLoop | 122.9337 ns | 1.4294 ns | 1.3371 ns | 0.1118 | - | - | 352 B | | |
| Span | 98.4519 ns | 0.5462 ns | 0.4842 ns | - | - | - | - | | |
| IndexOf | 188.1185 ns | 3.5552 ns | 3.3255 ns | 0.1626 | - | - | 512 B | | |
| SpanAndIndexOf | 34.3929 ns | 0.1206 ns | 0.1128 ns | - | - | - | - | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment