Skip to content

Conversation

@EgorBo
Copy link
Member

@EgorBo EgorBo commented Oct 27, 2025

I wasn't able to detect any difference for Path.GetRelativePath and the codegen is fairly close.
if we want extra perf, we can vectorize this routine trivially for Linux/macOS (where paths are case-sensitives).

Given this API allocates a new string, it probably isn't supposed to be used on a hot path.

@github-actions github-actions bot added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Oct 27, 2025
@EgorBo
Copy link
Member Author

EgorBo commented Oct 27, 2025

@MihuBot

@EgorBo
Copy link
Member Author

EgorBo commented Oct 27, 2025

@EgorBot -windows_intel -intel -arm

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

public class Benchmarks
{
    static void Main(string[] args)
    {
        BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);
    }

    public static IEnumerable<object> TestData()
    {
        yield return new object[] { @"C:\a", @"C:\a\b" };
        yield return new object[] { @"C:\a\", @"C:\a\b\c" };
        yield return new object[] {
            @"C:\projects\runtime\src\libraries",
            @"C:\projects\runtime\src\libraries\System.Text.Json\tests"
        };
        yield return new object[] {
            @"C:\a\b\c\d\e\f\g\h\i\j\k\l\m\n\o\p",
            @"C:\a\b\c\d\e\f\g\h\i\j\k\l\m\n\o\p\q\r\s\t\u\v\w\x\y\z"
        };
        yield return new object[] {
            @"C:\projects\runtime-main2\src\libraries\Microsoft.Extensions.DependencyInjection.Specification.Tests\src\Fakes",
            @"C:\projects\runtime-main2\src\libraries\Microsoft.Extensions.DependencyInjection.Specification.Tests\src\"
        };
        yield return new object[] { @"C:\foo\bar", @"D:\foo\bar" };
        yield return new object[] { @"C:\foo\bar\baz", @"C:\foo\bar\..\qux\." };
        yield return new object[] { @"\\server\share\folder1", @"\\server\share\folder1\folder2\folder3" };
        yield return new object[] {
            @"C:\Program Files\MyApp",
            @"C:\Program Files\MyApp\bin\Debug\net8.0"
        };
    }

    [Benchmark]
    [ArgumentsSource(nameof(TestData))]
    public string GetFoo(string relativeTo, string path) => 
        Path.GetRelativePath(relativeTo, path);
}

@EgorBo
Copy link
Member Author

EgorBo commented Oct 27, 2025

@EgorBot -intel -arm

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

public class Benchmarks
{
    static void Main(string[] args)
    {
        BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);
    }

    public static IEnumerable<object> TestData()
    {
        yield return new object[] { @"C:\a", @"C:\a\b" };
        yield return new object[] { @"C:\a\", @"C:\a\b\c" };
        yield return new object[] {
            @"C:\projects\runtime\src\libraries",
            @"C:\projects\runtime\src\libraries\System.Text.Json\tests"
        };
        yield return new object[] {
            @"C:\a\b\c\d\e\f\g\h\i\j\k\l\m\n\o\p",
            @"C:\a\b\c\d\e\f\g\h\i\j\k\l\m\n\o\p\q\r\s\t\u\v\w\x\y\z"
        };
        yield return new object[] {
            @"C:\projects\runtime-main2\src\libraries\Microsoft.Extensions.DependencyInjection.Specification.Tests\src\Fakes",
            @"C:\projects\runtime-main2\src\libraries\Microsoft.Extensions.DependencyInjection.Specification.Tests\src\"
        };
        yield return new object[] { @"C:\foo\bar", @"D:\foo\bar" };
        yield return new object[] { @"C:\foo\bar\baz", @"C:\foo\bar\..\qux\." };
        yield return new object[] { @"\\server\share\folder1", @"\\server\share\folder1\folder2\folder3" };
        yield return new object[] {
            @"C:\Program Files\MyApp",
            @"C:\Program Files\MyApp\bin\Debug\net8.0"
        };
    }

    [Benchmark]
    [ArgumentsSource(nameof(TestData))]
    public string GetFoo(string relativeTo, string path) => 
        Path.GetRelativePath(relativeTo, path);
}

Copy link
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

This PR modernizes the EqualStartingCharacterCount method by removing unsafe pointer arithmetic and replacing it with safe span-based operations. The implementation now leverages CommonPrefixLength for case-sensitive comparisons and falls back to a simple loop with char.ToUpperInvariant for case-insensitive scenarios.

Key changes:

  • Replaced unsafe pointer-based character comparison with safe span operations
  • Utilized AsSpan().CommonPrefixLength() for case-sensitive prefix matching
  • Simplified case-insensitive comparison logic using indexed access instead of pointer arithmetic

@dotnet-policy-service
Copy link
Contributor

Tagging subscribers to this area: @dotnet/area-system-runtime
See info in area-owners.md if you want to be subscribed.

@EgorBo EgorBo force-pushed the safe-EqualStartingCharacterCount branch from fbac4b0 to d7b1de6 Compare October 27, 2025 11:31
Co-authored-by: xtqqczze <45661989+xtqqczze@users.noreply.github.com>
@EgorBo
Copy link
Member Author

EgorBo commented Oct 28, 2025

/ba-g deadletter

@EgorBo EgorBo merged commit 91f3eb9 into dotnet:main Oct 28, 2025
84 of 86 checks passed
@EgorBo EgorBo deleted the safe-EqualStartingCharacterCount branch October 28, 2025 13:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants