Skip to content

Remove unsafe from EqualStartingCharacterCount#121102

Merged
EgorBo merged 7 commits into
dotnet:mainfrom
EgorBo:safe-EqualStartingCharacterCount
Oct 28, 2025
Merged

Remove unsafe from EqualStartingCharacterCount#121102
EgorBo merged 7 commits into
dotnet:mainfrom
EgorBo:safe-EqualStartingCharacterCount

Conversation

@EgorBo

@EgorBo EgorBo commented Oct 27, 2025

Copy link
Copy Markdown
Member

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

EgorBo commented Oct 27, 2025

Copy link
Copy Markdown
Member Author

@MihuBot

Comment thread src/libraries/Common/src/System/IO/PathInternal.cs Outdated
@EgorBo

EgorBo commented Oct 27, 2025

Copy link
Copy Markdown
Member Author

@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);
}

Comment thread src/libraries/Common/src/System/IO/PathInternal.cs
@EgorBo

EgorBo commented Oct 27, 2025

Copy link
Copy Markdown
Member Author

@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);
}

Comment thread src/libraries/Common/src/System/IO/PathInternal.cs Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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

Comment thread src/libraries/Common/src/System/IO/PathInternal.cs Outdated
@dotnet-policy-service

Copy link
Copy Markdown
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
Comment thread src/libraries/Common/src/System/IO/PathInternal.cs
Comment thread src/libraries/Common/src/System/IO/PathInternal.cs Outdated
Comment thread src/libraries/Common/src/System/IO/PathInternal.cs Outdated
@EgorBo

EgorBo commented Oct 28, 2025

Copy link
Copy Markdown
Member Author

/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
@github-actions github-actions Bot locked and limited conversation to collaborators Nov 28, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants