Skip to content

Commit 347595c

Browse files
committed
Use StringComparison.Ordinal in hot paths
1 parent 0331cd1 commit 347595c

File tree

8 files changed

+157
-10
lines changed

8 files changed

+157
-10
lines changed

src/Umbraco.Core/Extensions/UriExtensions.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public static class UriExtensions
2222
/// <remarks>Everything else remains unchanged, except for the fragment which is removed.</remarks>
2323
public static Uri Rewrite(this Uri uri, string path)
2424
{
25-
if (path.StartsWith("/") == false)
25+
if (path.StartsWith("/",StringComparison.Ordinal) == false)
2626
{
2727
throw new ArgumentException("Path must start with a slash.", "path");
2828
}
@@ -42,12 +42,12 @@ public static Uri Rewrite(this Uri uri, string path)
4242
/// <remarks>Everything else remains unchanged, except for the fragment which is removed.</remarks>
4343
public static Uri Rewrite(this Uri uri, string path, string query)
4444
{
45-
if (path.StartsWith("/") == false)
45+
if (path.StartsWith("/",StringComparison.Ordinal) == false)
4646
{
4747
throw new ArgumentException("Path must start with a slash.", "path");
4848
}
4949

50-
if (query.Length > 0 && query.StartsWith("?") == false)
50+
if (query.Length > 0 && query.StartsWith("?",StringComparison.Ordinal) == false)
5151
{
5252
throw new ArgumentException("Query must start with a question mark.", "query");
5353
}

src/Umbraco.Core/Routing/DomainUtilities.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,7 @@ private static IReadOnlyCollection<DomainAndUri> SelectByBase(IReadOnlyCollectio
388388
public static Uri ParseUriFromDomainName(string domainName, Uri currentUri)
389389
{
390390
// turn "/en" into "http://whatever.com/en" so it becomes a parseable uri
391-
var name = domainName.StartsWith("/") && currentUri != null
391+
var name = domainName.StartsWith("/",StringComparison.Ordinal) && currentUri != null
392392
? currentUri.GetLeftPart(UriPartial.Authority) + domainName
393393
: domainName;
394394
var scheme = currentUri?.Scheme ?? Uri.UriSchemeHttp;

src/Umbraco.Core/Routing/NewDefaultUrlProvider.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ public virtual IEnumerable<UrlInfo> GetOtherUrls(int id, Uri current)
155155
}
156156

157157
// need to strip off the leading ID for the route if it exists (occurs if the route is for a node with a domain assigned)
158-
var pos = route.IndexOf('/');
158+
var pos = route.IndexOf('/', StringComparison.Ordinal);
159159
var path = pos == 0 ? route : route.Substring(pos);
160160

161161
var uri = new Uri(CombinePaths(d.Uri.GetLeftPart(UriPartial.Path), path));
@@ -237,7 +237,7 @@ private string GetLegacyRouteFormatById(Guid key, string? culture)
237237

238238
// extract domainUri and path
239239
// route is /<path> or <domainRootId>/<path>
240-
var pos = route.IndexOf('/');
240+
var pos = route.IndexOf('/', StringComparison.Ordinal);
241241
var path = pos == 0 ? route : route[pos..];
242242
DomainAndUri? domainUri = pos == 0
243243
? null

src/Umbraco.Core/Routing/PublishedRouter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -440,7 +440,7 @@ internal bool FindTemplateRenderingEngineInDirectory(DirectoryInfo? directory, s
440440
return false;
441441
}
442442

443-
var pos = alias.IndexOf('/');
443+
var pos = alias.IndexOf('/',StringComparison.Ordinal);
444444
if (pos > 0)
445445
{
446446
// recurse

src/Umbraco.Core/Routing/UriUtility.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ public string ResolveUrl(string relativeUrl)
144144
var idxOfScheme = relativeUrl.IndexOf(@"://", StringComparison.Ordinal);
145145
if (idxOfScheme != -1)
146146
{
147-
var idxOfQM = relativeUrl.IndexOf('?');
147+
var idxOfQM = relativeUrl.IndexOf('?',StringComparison.Ordinal);
148148
if (idxOfQM == -1 || idxOfQM > idxOfScheme)
149149
{
150150
return relativeUrl;
@@ -226,7 +226,7 @@ internal Uri ToFullUrl(string absolutePath, Uri curentRequestUrl)
226226
throw new ArgumentNullException(nameof(absolutePath));
227227
}
228228

229-
if (!absolutePath.StartsWith("/"))
229+
if (!absolutePath.StartsWith("/", StringComparison.Ordinal))
230230
{
231231
throw new FormatException("The absolutePath specified does not start with a '/'");
232232
}

src/Umbraco.Core/UriUtilityCore.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ namespace Umbraco.Cms.Core;
44

55
public static class UriUtilityCore
66
{
7-
public static bool HasScheme(string uri) => uri.IndexOf("://", StringComparison.InvariantCulture) > 0;
7+
public static bool HasScheme(string uri) => uri.IndexOf("://", StringComparison.Ordinal) > 0;
88

99
public static string StartWithScheme(string uri) => StartWithScheme(uri, null);
1010

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
using BenchmarkDotNet.Attributes;
2+
using Umbraco.Tests.Benchmarks.Config;
3+
4+
namespace Umbraco.Tests.Benchmarks;
5+
6+
[QuickRunWithMemoryDiagnoserConfig]
7+
public class StringIndexOfBenchmarks
8+
{
9+
private string _domainName = "https://www.lorem-ipsum.com";
10+
11+
[Benchmark()]
12+
public bool IndexOf_Original()
13+
{
14+
return _domainName.IndexOf("://") > 0;
15+
}
16+
17+
[Benchmark()]
18+
public bool IndexOf_Ordinal()
19+
{
20+
return _domainName.IndexOf("://",StringComparison.Ordinal) > -1;
21+
}
22+
23+
[Benchmark()]
24+
public bool IndexOf_Invariant()
25+
{
26+
return _domainName.IndexOf("://", StringComparison.InvariantCulture) > -1;
27+
}
28+
29+
[Benchmark()]
30+
public bool IndexOf_Span()
31+
{
32+
return _domainName.AsSpan().IndexOf("://", StringComparison.InvariantCulture) > -1;
33+
}
34+
35+
[Benchmark()]
36+
public bool Contains()
37+
{
38+
return _domainName.Contains("://");
39+
}
40+
41+
[Benchmark()]
42+
public bool Contains_Ordinal()
43+
{
44+
return _domainName.Contains("://",StringComparison.Ordinal);
45+
}
46+
47+
[Benchmark()]
48+
public bool Contains_Invariant()
49+
{
50+
return _domainName.Contains("://", StringComparison.InvariantCulture);
51+
}
52+
53+
[Benchmark()]
54+
public bool Contains_Span_Ordinal()
55+
{
56+
return _domainName.AsSpan().Contains("://", StringComparison.Ordinal);
57+
}
58+
59+
[Benchmark()]
60+
public bool Contains_Span_Invariant()
61+
{
62+
return _domainName.AsSpan().Contains("://", StringComparison.InvariantCulture);
63+
}
64+
65+
[Benchmark()]
66+
public bool Span_Index_Of()
67+
{
68+
var uri = "https://www.lorem-ipsum.com".AsSpan();
69+
return uri.IndexOf("#") > -1;
70+
}
71+
72+
[Benchmark()]
73+
public bool Span_Index_Of_Ordinal()
74+
{
75+
var uri = "https://www.lorem-ipsum.com".AsSpan();
76+
return uri.IndexOf("#".AsSpan(),StringComparison.Ordinal) > -1;
77+
}
78+
79+
/*
80+
| Method | Mean | Error | StdDev | Allocated |
81+
|------------------------ |-----------:|-----------:|----------:|----------:|
82+
| IndexOf_Original | 916.918 ns | 73.7556 ns | 4.0428 ns | - |
83+
| IndexOf_Ordinal | 4.083 ns | 1.5083 ns | 0.0827 ns | - |
84+
| IndexOf_Invariant | 12.941 ns | 3.7574 ns | 0.2060 ns | - |
85+
| IndexOf_Span | 13.076 ns | 3.0666 ns | 0.1681 ns | - |
86+
| Contains | 2.828 ns | 0.3648 ns | 0.0200 ns | - |
87+
| Contains_Ordinal | 4.368 ns | 0.9882 ns | 0.0542 ns | - |
88+
| Contains_Invariant | 12.986 ns | 2.3526 ns | 0.1290 ns | - |
89+
| Contains_Span_Ordinal | 2.924 ns | 0.1593 ns | 0.0087 ns | - |
90+
| Contains_Span_Invariant | 12.502 ns | 1.4153 ns | 0.0776 ns | - |
91+
| Span_Index_Of | 1.741 ns | 0.9093 ns | 0.0498 ns | - |
92+
| Span_Index_Of_Ordinal | 1.809 ns | 0.3703 ns | 0.0203 ns | - |
93+
*/
94+
95+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
using BenchmarkDotNet.Attributes;
2+
using Umbraco.Tests.Benchmarks.Config;
3+
4+
namespace Umbraco.Tests.Benchmarks;
5+
6+
[QuickRunWithMemoryDiagnoserConfig]
7+
public class StringStartsWithBenchmarks
8+
{
9+
10+
private string _domainName = "domain1.com";
11+
12+
[Benchmark(Baseline = true)]
13+
public bool Original()
14+
{
15+
return _domainName.StartsWith("/");
16+
}
17+
18+
[Benchmark()]
19+
public bool Ordinal()
20+
{
21+
return _domainName.StartsWith("/",StringComparison.Ordinal);
22+
}
23+
24+
[Benchmark()]
25+
public bool Invariant()
26+
{
27+
return _domainName.StartsWith("/", StringComparison.InvariantCulture);
28+
}
29+
30+
[Benchmark()]
31+
public bool FirstChar()
32+
{
33+
return _domainName.Length > 0 && _domainName[0] == '/';
34+
}
35+
36+
[Benchmark()]
37+
public bool Span()
38+
{
39+
return _domainName.AsSpan().StartsWith("/".AsSpan(),StringComparison.Ordinal);
40+
}
41+
42+
/*
43+
| Method | Mean | Error | StdDev | Allocated |
44+
|---------- |------------:|-----------:|----------:|----------:|
45+
| Original | 255.2239 ns | 10.9432 ns | 0.5998 ns | - |
46+
| Ordinal | 0.1784 ns | 0.3070 ns | 0.0168 ns | - |
47+
| Invariant | 4.1270 ns | 0.4990 ns | 0.0274 ns | - |
48+
| FirstChar | 0.0127 ns | 0.0098 ns | 0.0005 ns | - |
49+
| Span | 0.8000 ns | 0.4526 ns | 0.0248 ns | - |
50+
*/
51+
52+
}

0 commit comments

Comments
 (0)