Skip to content

Refactor FrameworkFileUtilities for better performance#13143

Merged
AR-May merged 7 commits intodotnet:mainfrom
AR-May:optimize-file-utilities
Feb 6, 2026
Merged

Refactor FrameworkFileUtilities for better performance#13143
AR-May merged 7 commits intodotnet:mainfrom
AR-May:optimize-file-utilities

Conversation

@AR-May
Copy link
Copy Markdown
Member

@AR-May AR-May commented Jan 29, 2026

Context

Refactoring the AbsoluteClass functions in FrameworkFileUtilities. Avoid to create a new struct when no changes needed.

Copilot AI review requested due to automatic review settings January 29, 2026 13:42
@AR-May
Copy link
Copy Markdown
Member Author

AR-May commented Jan 29, 2026

Perf numbers (V2 is the new implementation), measured on butch of paths:

Method Job Runtime Mean Error StdDev Rank Gen0 Allocated
EnsureTrailingSlash_String_Benchmark .NET 10.0 .NET 10.0 73.325 us 1.2113 us 1.1331 us 7 41.0156 688000 B
EnsureTrailingSlash_Benchmark .NET 10.0 .NET 10.0 72.115 us 1.2360 us 1.4234 us 7 41.0156 688000 B
EnsureTrailingSlashV2_Benchmark .NET 10.0 .NET 10.0 73.223 us 1.3811 us 3.2553 us 7 41.0156 688000 B
EnsureNoTrailingSlash_String_Benchmark .NET 10.0 .NET 10.0 37.470 us 0.6402 us 0.5988 us 4 18.6157 312000 B
EnsureNoTrailingSlash_Benchmark .NET 10.0 .NET 10.0 37.656 us 0.2981 us 0.2643 us 4 18.6157 312000 B
EnsureNoTrailingSlashV2_Benchmark .NET 10.0 .NET 10.0 35.222 us 0.6599 us 1.2556 us 3 18.6157 312000 B
NormalizePath_String_Benchmark .NET 10.0 .NET 10.0 1,317.008 us 3.3271 us 2.5976 us 15 21.4844 392000 B
NormalizePath_Benchmark .NET 10.0 .NET 10.0 1,288.837 us 6.3469 us 5.6263 us 15 21.4844 392000 B
NormalizePathV2_Benchmark .NET 10.0 .NET 10.0 747.074 us 4.4119 us 3.9111 us 14 22.4609 392000 B
RemoveRelativeSegments_Benchmark .NET 10.0 .NET 10.0 1,300.154 us 5.1325 us 4.8009 us 15 21.4844 392000 B
RemoveRelativeSegmentsV2_Benchmark .NET 10.0 .NET 10.0 728.501 us 2.2627 us 2.1165 us 14 22.4609 392000 B
FixFilePath_String_Benchmark .NET 10.0 .NET 10.0 2.901 us 0.0122 us 0.0108 us 1 - -
FixFilePath_Benchmark .NET 10.0 .NET 10.0 2.895 us 0.0037 us 0.0033 us 1 - -
FixFilePathV2_Benchmark .NET 10.0 .NET 10.0 2.912 us 0.0196 us 0.0173 us 1 - -
EnsureTrailingSlash_String_Benchmark .NET Framework 4.7.2 .NET Framework 4.7.2 203.820 us 1.3756 us 1.2868 us 11 150.3906 946781 B
EnsureTrailingSlash_Benchmark .NET Framework 4.7.2 .NET Framework 4.7.2 251.351 us 0.8598 us 0.8042 us 12 150.3906 946782 B
EnsureTrailingSlashV2_Benchmark .NET Framework 4.7.2 .NET Framework 4.7.2 263.041 us 1.0587 us 0.9385 us 13 150.3906 946782 B
EnsureNoTrailingSlash_String_Benchmark .NET Framework 4.7.2 .NET Framework 4.7.2 86.069 us 0.3852 us 0.3415 us 8 49.6826 312922 B
EnsureNoTrailingSlash_Benchmark .NET Framework 4.7.2 .NET Framework 4.7.2 126.500 us 0.3002 us 0.2808 us 10 49.5605 312921 B
EnsureNoTrailingSlashV2_Benchmark .NET Framework 4.7.2 .NET Framework 4.7.2 108.388 us 0.3628 us 0.3394 us 9 49.6826 312922 B
NormalizePath_String_Benchmark .NET Framework 4.7.2 .NET Framework 4.7.2 2,584.856 us 9.2089 us 7.6898 us 17 62.5000 401160 B
NormalizePath_Benchmark .NET Framework 4.7.2 .NET Framework 4.7.2 2,601.561 us 11.0698 us 9.2438 us 17 62.5000 401160 B
NormalizePathV2_Benchmark .NET Framework 4.7.2 .NET Framework 4.7.2 2,523.374 us 4.5077 us 4.2165 us 16 62.5000 401160 B
RemoveRelativeSegments_Benchmark .NET Framework 4.7.2 .NET Framework 4.7.2 2,605.943 us 8.7065 us 8.1441 us 17 62.5000 401160 B
RemoveRelativeSegmentsV2_Benchmark .NET Framework 4.7.2 .NET Framework 4.7.2 2,591.876 us 5.4804 us 4.8583 us 17 62.5000 401160 B
FixFilePath_String_Benchmark .NET Framework 4.7.2 .NET Framework 4.7.2 16.978 us 0.0174 us 0.0154 us 2 - -
FixFilePath_Benchmark .NET Framework 4.7.2 .NET Framework 4.7.2 62.076 us 0.0538 us 0.0477 us 6 - -
FixFilePathV2_Benchmark .NET Framework 4.7.2 .NET Framework 4.7.2 53.760 us 0.0971 us 0.0811 us 5 - -

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Purpose: This PR refactors the FrameworkFileUtilities class to improve performance by avoiding unnecessary struct allocations. The refactoring adds early-return optimizations to AbsolutePath overload methods that check if modifications are needed before creating new instances.

Changes:

  • Added helper methods to detect when path normalization is needed (HasWindowsDirectorySeparatorOnUnix, HasUnixDirectorySeparatorOnWindows, HasRelativeSegment)
  • Added early-return optimizations to AbsolutePath overload methods to avoid creating new instances when no changes are needed
  • Removed the EndsWithSlash method and inlined its logic where used within the file
Comments suppressed due to low confidence (2)

src/Framework/FileUtilities.cs:66

  • The EndsWithSlash method is removed but there are still multiple external references to FrameworkFileUtilities.EndsWithSlash throughout the codebase. This will cause compilation failures. References exist in:
  • src/Build/Definition/Toolset.cs (line 369)
  • src/Shared/FileUtilities.cs (lines 694, 789)
  • src/Shared/Modifiers.cs (line 217)
  • src/Utilities/TrackedDependencies/FileTracker.cs (line 618)
  • src/Shared/UnitTests/FileUtilities_Tests.cs (multiple lines)
  • src/Shared/UnitTests/FileMatcher_Tests.cs (line 2302)

Either restore the method or update all external references to use the inlined equivalent (path.Length > 0 && IsSlash(path[path.Length - 1])).

        /// <summary>
        /// Fixes backslashes to forward slashes on Unix. This allows to recognise windows style paths on Unix. 
        /// However, this leads to incorrect path on Linux if backslash was part of the file/directory name.
        /// </summary>  
        internal static string FixFilePath(string path)
        {
            return string.IsNullOrEmpty(path) || Path.DirectorySeparatorChar == WindowsDirectorySeparator ? path : path.Replace(WindowsDirectorySeparator, UnixDirectorySeparator);
        }

        /// <summary>
        /// If the given path doesn't have a trailing slash then add one.
        /// If the path is an empty string, does not modify it.
        /// </summary>

src/Framework/FileUtilities.cs:121

  • The new optimization logic in the AbsolutePath overloads (specifically the early return paths that avoid allocating new instances) lacks test coverage. Consider adding tests to verify:
  1. That the method returns the same instance when no modification is needed
  2. That the method creates a new instance when modification is needed
  3. Edge cases like empty paths, paths with/without trailing slashes, and paths with different separator types on Unix vs Windows

These optimizations are the core value of this PR, so they should be thoroughly tested.

        internal static AbsolutePath EnsureTrailingSlash(AbsolutePath path)
        {
            if (string.IsNullOrEmpty(path.Value))
            {
                return path;
            }

            // Check if already has trailing slash and no separator fixing needed on unix 
            // (EnsureTrailingSlash also should fix the paths on unix). 
            if (IsSlash(path.Value[path.Value.Length - 1]) && !HasWindowsDirectorySeparatorOnUnix(path.Value))
            {
                return path;
            }

            return new AbsolutePath(EnsureTrailingSlash(path.Value), 
                original: path.OriginalValue, 
                ignoreRootedCheck: true);
        }

AR-May and others added 5 commits January 29, 2026 14:53
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@AR-May AR-May merged commit bf72590 into dotnet:main Feb 6, 2026
10 checks passed
JanProvaznik pushed a commit to JanProvaznik/msbuild that referenced this pull request Feb 25, 2026
### Context
Refactoring the AbsoluteClass functions in FrameworkFileUtilities. Avoid
to create a new struct when no changes needed.

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants