Skip to content

Instantly share code, notes, and snippets.

@loic-sharma
Last active September 11, 2019 06:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save loic-sharma/c10bc598fe83dbb15fcff3d0fa64a423 to your computer and use it in GitHub Desktop.
Save loic-sharma/c10bc598fe83dbb15fcff3d0fa64a423 to your computer and use it in GitHub Desktop.
Improving NuGetVersion parse performance
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; }
}
}
}
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 | - | - | - | - |
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