From c04d30599adf7226f0c05de53d14119d3617ec79 Mon Sep 17 00:00:00 2001 From: "Erik A. Brandstadmoen" Date: Wed, 2 Oct 2019 16:37:34 +0200 Subject: [PATCH 1/2] Performance improvements - profiling and caching Used the profiler to find hotspots, most were in Regex matching of commit messages. Added simple static cache to IncrementStrategyFinder. Runtime halved on medium-sized git codebase (~3 years of history). --- src/GitVersionCore/IncrementStrategyFinder.cs | 51 ++++++++++++++----- .../BaseVersionCalculators/BaseVersion.cs | 2 +- 2 files changed, 40 insertions(+), 13 deletions(-) diff --git a/src/GitVersionCore/IncrementStrategyFinder.cs b/src/GitVersionCore/IncrementStrategyFinder.cs index c4c75e18a8..2d58b70add 100644 --- a/src/GitVersionCore/IncrementStrategyFinder.cs +++ b/src/GitVersionCore/IncrementStrategyFinder.cs @@ -69,13 +69,16 @@ public static class IncrementStrategyFinder public static VersionField? GetIncrementForCommits(GitVersionContext context, IEnumerable commits) { - var majorRegex = CreateRegex(context.Configuration.MajorVersionBumpMessage ?? DefaultMajorPattern); - var minorRegex = CreateRegex(context.Configuration.MinorVersionBumpMessage ?? DefaultMinorPattern); - var patchRegex = CreateRegex(context.Configuration.PatchVersionBumpMessage ?? DefaultPatchPattern); - var none = CreateRegex(context.Configuration.NoBumpMessage ?? DefaultNoBumpPattern); + // More efficient use of Regexes. The static version of Regex.IsMatch caches the compiled regexes. + // see: https://docs.microsoft.com/en-us/dotnet/standard/base-types/best-practices#static-regular-expressions + + var majorRegex = context.Configuration.MajorVersionBumpMessage ?? DefaultMajorPattern; + var minorRegex = context.Configuration.MinorVersionBumpMessage ?? DefaultMinorPattern; + var patchRegex = context.Configuration.PatchVersionBumpMessage ?? DefaultPatchPattern; + var none = context.Configuration.NoBumpMessage ?? DefaultNoBumpPattern; var increments = commits - .Select(c => FindIncrementFromMessage(c.Message, majorRegex, minorRegex, patchRegex, none)) + .Select(c => GetIncrementFromMessage(c.Message, majorRegex, minorRegex, patchRegex, none)) .Where(v => v != null) .Select(v => v.Value) .ToList(); @@ -88,6 +91,8 @@ public static class IncrementStrategyFinder return null; } + + private static IEnumerable GetIntermediateCommits(IRepository repo, Commit baseCommit, Commit headCommit) { if (baseCommit == null) yield break; @@ -114,21 +119,43 @@ private static IEnumerable GetIntermediateCommits(IRepository repo, Comm } } - private static VersionField? FindIncrementFromMessage(string message, Regex major, Regex minor, Regex patch, Regex none) + private static VersionField? GetIncrementFromMessage(string message, string majorRegex, string minorRegex, string patchRegex, string none) { - if (major.IsMatch(message)) return VersionField.Major; - if (minor.IsMatch(message)) return VersionField.Minor; - if (patch.IsMatch(message)) return VersionField.Patch; - if (none.IsMatch(message)) return VersionField.None; + var key = message.GetHashCode(); + + if (!VersionFieldCache.TryGetValue(key, out var version)) + { + version = FindIncrementFromMessage(message, majorRegex, minorRegex, patchRegex, none); + VersionFieldCache[key] = version; + } + return version; + } + + private static VersionField? FindIncrementFromMessage(string message, string majorRegex, string minorRegex, string patchRegex, string noneRegex) + { + if(IsMatch(message, majorRegex)) return VersionField.Major; + if(IsMatch(message, minorRegex)) return VersionField.Minor; + if(IsMatch(message, patchRegex)) return VersionField.Patch; + if(IsMatch(message, noneRegex)) return VersionField.None; return null; } - private static Regex CreateRegex(string pattern) + private static bool IsMatch(string message, string regex) { - return new Regex(pattern, RegexOptions.Compiled | RegexOptions.IgnoreCase); + var key = message.GetHashCode() ^ regex.GetHashCode(); + + if (!MatchCache.TryGetValue(key, out var match)) + { + match = Regex.IsMatch(message, regex, RegexOptions.IgnoreCase); + MatchCache[key] = match; + } + return match; } + private static IDictionary MatchCache = new Dictionary(); + private static IDictionary VersionFieldCache = new Dictionary(); + public static VersionField FindDefaultIncrementForBranch( GitVersionContext context, string branch = null ) { var config = context.FullConfiguration.GetConfigForBranch(branch ?? context.CurrentBranch.NameWithoutRemote()); diff --git a/src/GitVersionCore/VersionCalculation/BaseVersionCalculators/BaseVersion.cs b/src/GitVersionCore/VersionCalculation/BaseVersionCalculators/BaseVersion.cs index 72739762c0..02b204da5c 100644 --- a/src/GitVersionCore/VersionCalculation/BaseVersionCalculators/BaseVersion.cs +++ b/src/GitVersionCore/VersionCalculation/BaseVersionCalculators/BaseVersion.cs @@ -31,4 +31,4 @@ public override string ToString() return $"{Source}: {SemanticVersion.ToString("f")} with commit count source {(BaseVersionSource == null ? "External Source" : BaseVersionSource.Sha)} (Incremented: {(ShouldIncrement ? BaseVersionCalculator.MaybeIncrement(_context, this).ToString("t") : "None")})"; } } -} \ No newline at end of file +} From f480a3ac17b2f2fbaf7d4a0a4afee39121c954c4 Mon Sep 17 00:00:00 2001 From: "Erik A. Brandstadmoen" Date: Wed, 2 Oct 2019 23:43:13 +0200 Subject: [PATCH 2/2] Explicitly said 'LibGet2Sharp.Index' to avoid confusion with System.Index (build failed on Linux) --- src/GitVersionCore.Tests/Mocks/MockRepository.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GitVersionCore.Tests/Mocks/MockRepository.cs b/src/GitVersionCore.Tests/Mocks/MockRepository.cs index add8bf1684..965cdb93bd 100644 --- a/src/GitVersionCore.Tests/Mocks/MockRepository.cs +++ b/src/GitVersionCore.Tests/Mocks/MockRepository.cs @@ -211,7 +211,7 @@ public void RevParse(string revision, out Reference reference, out GitObject obj public Branch Head { get; set; } public LibGit2Sharp.Configuration Config { get; set; } - public Index Index { get; set; } + public LibGit2Sharp.Index Index { get; set; } public ReferenceCollection Refs { get; set; } public IQueryableCommitLog Commits