Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Conversation

@stephentoub
Copy link
Member

In the common case where it need to replace a non-empty extension with a non-empty extension, it currently incurs a substring without the original extension prior to then concatenating with the new extension. This PR avoids that.

(As the Path implementation is in corelib, this uses string.FastAllocateString and then formats into it with a span; if we wanted to avoid that, string.Create could also be used. That could also be addressed with new String.Concat overloads that accept ReadOnlySpan<char>s.)

Benchmark:

[Benchmark] public string ChangeExtensionReplace() => Path.ChangeExtension(@"c:\this\is\a\path\hello.txt", ".dat");
[Benchmark] public string ChangeExtensionRemove() => Path.ChangeExtension(@"c:\this\is\a\path\hello.txt", null);

Before:

                 Method |     Mean |     Error |    StdDev |  Gen 0 | Allocated |
----------------------- |---------:|----------:|----------:|-------:|----------:|
 ChangeExtensionReplace | 41.39 ns | 1.4013 ns | 2.7985 ns | 0.0363 |     152 B |
  ChangeExtensionRemove | 18.57 ns | 0.2763 ns | 0.2449 ns | 0.0172 |      72 B |

After:

                 Method |     Mean |     Error |    StdDev |  Gen 0 | Allocated |
----------------------- |---------:|----------:|----------:|-------:|----------:|
 ChangeExtensionReplace | 18.66 ns | 0.1989 ns | 0.1764 ns | 0.0191 |      80 B |
  ChangeExtensionRemove | 18.81 ns | 0.2678 ns | 0.2505 ns | 0.0172 |      72 B |

cc: @JeremyKuhne, @jkotas

@stephentoub
Copy link
Member Author

@dotnet-bot test Ubuntu x64 Checked Innerloop Build and Test please
@dotnet-bot test Windows_NT arm64 Cross Debug Innerloop Build please

@jkotas
Copy link
Member

jkotas commented Jan 2, 2019

Is this important enough method to add extra more complex code to avoid the allocations?

The Range/Index fix was close to my threashold, but I think this one is below it.

@stephentoub
Copy link
Member Author

Is this important enough method to add extra more complex code to avoid the allocations?

It may not be. I can close if you don't think it's worth it. I'd searched msbuild to see its usage (https://github.com/Microsoft/msbuild/search?p=1&q=ChangeExtension&unscoped_q=ChangeExtension) and some of those seemed like they might be meaningful, but no smoking gun. (Also http://index/?leftProject=mscorlib&leftSymbol=vf50vlj9fsry&file=system%5Cio%5Cpath.cs for folks with access.)

@jkotas
Copy link
Member

jkotas commented Jan 3, 2019

This change would look fine to me if it used Concat that takes Spans. Do we have API issue for that?

I think Concat that takes Spans would be useful. I have run myself into situations where it would be useful to have. Add the Concat as internal for now to support this change, and add API proposal to add it as public in parallel?

@stephentoub
Copy link
Member Author

Add the Concat as internal for now to support this change, and add API proposal to add it as public in parallel?

I was thinking the same thing. Will do.

In the common case where it need to replace a non-empty extension with a non-empty extension, it currently incurs a substring without the original extension prior to then concatenating with the new extension.  This PR avoids that.

(As the Path implementation is in corelib, this uses string.FastAllocateString and then formats into it with a span; if we wanted to avoid that, string.Create could also be used.)
@stephentoub
Copy link
Member Author

I added String.Concat overloads for spans, utilized them in a few places in corelib (including in Path.ChangeExtension), and opened https://github.com/dotnet/corefx/issues/34330 to make the methods public.

@jkotas
Copy link
Member

jkotas commented Jan 3, 2019

@dotnet-bot test Windows_NT x64 Release CoreFX Tests please

@jkotas jkotas merged commit 8f91ac8 into dotnet:master Jan 3, 2019
Dotnet-GitSync-Bot pushed a commit to Dotnet-GitSync-Bot/corert that referenced this pull request Jan 3, 2019
In the common case where it need to replace a non-empty extension with a non-empty extension, it currently incurs a substring without the original extension prior to then concatenating with the new extension.  This PR avoids that.

(As the Path implementation is in corelib, this uses string.FastAllocateString and then formats into it with a span; if we wanted to avoid that, string.Create could also be used.)

* Add internal String.Concat overloads for spans

* Use span-based string.Concat overloads in several places

Signed-off-by: dotnet-bot <[email protected]>
Dotnet-GitSync-Bot pushed a commit to Dotnet-GitSync-Bot/corefx that referenced this pull request Jan 3, 2019
In the common case where it need to replace a non-empty extension with a non-empty extension, it currently incurs a substring without the original extension prior to then concatenating with the new extension.  This PR avoids that.

(As the Path implementation is in corelib, this uses string.FastAllocateString and then formats into it with a span; if we wanted to avoid that, string.Create could also be used.)

* Add internal String.Concat overloads for spans

* Use span-based string.Concat overloads in several places

Signed-off-by: dotnet-bot <[email protected]>
@stephentoub stephentoub deleted the pathextension branch January 3, 2019 21:45
jkotas pushed a commit to dotnet/corert that referenced this pull request Jan 3, 2019
In the common case where it need to replace a non-empty extension with a non-empty extension, it currently incurs a substring without the original extension prior to then concatenating with the new extension.  This PR avoids that.

(As the Path implementation is in corelib, this uses string.FastAllocateString and then formats into it with a span; if we wanted to avoid that, string.Create could also be used.)

* Add internal String.Concat overloads for spans

* Use span-based string.Concat overloads in several places

Signed-off-by: dotnet-bot <[email protected]>
stephentoub added a commit to Dotnet-GitSync-Bot/corefx that referenced this pull request Jan 4, 2019
In the common case where it need to replace a non-empty extension with a non-empty extension, it currently incurs a substring without the original extension prior to then concatenating with the new extension.  This PR avoids that.

(As the Path implementation is in corelib, this uses string.FastAllocateString and then formats into it with a span; if we wanted to avoid that, string.Create could also be used.)

* Add internal String.Concat overloads for spans

* Use span-based string.Concat overloads in several places

Signed-off-by: dotnet-bot <[email protected]>
Dotnet-GitSync-Bot pushed a commit to Dotnet-GitSync-Bot/mono that referenced this pull request Jan 4, 2019
In the common case where it need to replace a non-empty extension with a non-empty extension, it currently incurs a substring without the original extension prior to then concatenating with the new extension.  This PR avoids that.

(As the Path implementation is in corelib, this uses string.FastAllocateString and then formats into it with a span; if we wanted to avoid that, string.Create could also be used.)

* Add internal String.Concat overloads for spans

* Use span-based string.Concat overloads in several places

Signed-off-by: dotnet-bot <[email protected]>
marek-safar pushed a commit to mono/mono that referenced this pull request Jan 4, 2019
In the common case where it need to replace a non-empty extension with a non-empty extension, it currently incurs a substring without the original extension prior to then concatenating with the new extension.  This PR avoids that.

(As the Path implementation is in corelib, this uses string.FastAllocateString and then formats into it with a span; if we wanted to avoid that, string.Create could also be used.)

* Add internal String.Concat overloads for spans

* Use span-based string.Concat overloads in several places

Signed-off-by: dotnet-bot <[email protected]>
stephentoub added a commit to dotnet/corefx that referenced this pull request Jan 4, 2019
In the common case where it need to replace a non-empty extension with a non-empty extension, it currently incurs a substring without the original extension prior to then concatenating with the new extension.  This PR avoids that.

(As the Path implementation is in corelib, this uses string.FastAllocateString and then formats into it with a span; if we wanted to avoid that, string.Create could also be used.)

* Add internal String.Concat overloads for spans

* Use span-based string.Concat overloads in several places

Signed-off-by: dotnet-bot <[email protected]>
@stephentoub stephentoub changed the title Improve performance of Path.ChangeExtension Add/use internal String.Concat overloads for spans May 7, 2019
picenka21 pushed a commit to picenka21/runtime that referenced this pull request Feb 18, 2022
In the common case where it need to replace a non-empty extension with a non-empty extension, it currently incurs a substring without the original extension prior to then concatenating with the new extension.  This PR avoids that.

(As the Path implementation is in corelib, this uses string.FastAllocateString and then formats into it with a span; if we wanted to avoid that, string.Create could also be used.)

* Add internal String.Concat overloads for spans

* Use span-based string.Concat overloads in several places


Commit migrated from dotnet/coreclr@8f91ac8
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants