Open
Description
Summary
UriHelper.BuildRelative
creates an intermediary string for the combined path that is used only for concatenating with the other components to create the final URL.
public static string BuildRelative(
PathString pathBase = new PathString(),
PathString path = new PathString(),
QueryString query = new QueryString(),
FragmentString fragment = new FragmentString())
{
string combinePath = (pathBase.HasValue || path.HasValue) ? (pathBase + path).ToString() : "/";
return combinePath + query.ToString() + fragment.ToString();
}
Motivation and goals
This method is frequently use in hot paths like redirect and rewrite rules.
Detailed design
Single_Concat
Given that the final URL is composed of only 3 or 4 parts, the use of string.Concat
is both time and memory efficient.
String_Create
string.Create
could be used. But it's just as memory efficient as string.Concat
and less time efficient is most cases.
Benchmarks
BenchmarkDotNet=v0.12.1, OS=Windows 10.0.21277
Intel Core i7-8650U CPU 1.90GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores
.NET Core SDK=5.0.200-preview.20614.14
[Host] : .NET Core 5.0.1 (CoreCLR 5.0.120.57516, CoreFX 5.0.120.57516), X64 RyuJIT
DefaultJob : .NET Core 5.0.1 (CoreCLR 5.0.120.57516, CoreFX 5.0.120.57516), X64 RyuJIT
Method | pathBase | path | query | fragment | Mean | Error | StdDev | Median | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
UriHelper_BuildRelative | 10.334 ns | 0.3064 ns | 0.4948 ns | 10.278 ns | 1.00 | 0.00 | - | - | - | - | ||||
Single_Concat | 7.557 ns | 0.1385 ns | 0.1295 ns | 7.534 ns | 0.71 | 0.04 | - | - | - | - | ||||
String_Create | 8.398 ns | 0.2614 ns | 0.4646 ns | 8.210 ns | 0.82 | 0.07 | - | - | - | - | ||||
UriHelper_BuildRelative | #fragment | 29.557 ns | 0.5777 ns | 0.8098 ns | 29.433 ns | 1.00 | 0.00 | 0.0114 | - | - | 48 B | |||
Single_Concat | #fragment | 25.987 ns | 0.5162 ns | 0.4576 ns | 25.986 ns | 0.87 | 0.04 | 0.0114 | - | - | 48 B | |||
String_Create | #fragment | 37.347 ns | 0.7226 ns | 0.9395 ns | 37.080 ns | 1.26 | 0.04 | 0.0114 | - | - | 48 B | |||
UriHelper_BuildRelative | ?param1=value1¶m2=value2¶m3=value3 | 99.900 ns | 1.4810 ns | 1.2367 ns | 99.743 ns | 1.00 | 0.00 | 0.0267 | - | - | 112 B | |||
Single_Concat | ?param1=value1¶m2=value2¶m3=value3 | 100.739 ns | 2.1000 ns | 3.4504 ns | 99.189 ns | 1.01 | 0.04 | 0.0267 | - | - | 112 B | |||
String_Create | ?param1=value1¶m2=value2¶m3=value3 | 108.600 ns | 1.1783 ns | 1.0445 ns | 108.788 ns | 1.09 | 0.02 | 0.0267 | - | - | 112 B | |||
UriHelper_BuildRelative | ?param1=value1¶m2=value2¶m3=value3 | #fragment | 103.621 ns | 1.6522 ns | 1.9668 ns | 103.039 ns | 1.00 | 0.00 | 0.0305 | - | - | 128 B | ||
Single_Concat | ?param1=value1¶m2=value2¶m3=value3 | #fragment | 103.340 ns | 2.1289 ns | 2.7682 ns | 102.687 ns | 1.00 | 0.03 | 0.0305 | - | - | 128 B | ||
String_Create | ?param1=value1¶m2=value2¶m3=value3 | #fragment | 114.593 ns | 2.3370 ns | 2.2953 ns | 114.116 ns | 1.10 | 0.03 | 0.0305 | - | - | 128 B | ||
UriHelper_BuildRelative | /path/one/two/three | 145.257 ns | 2.9859 ns | 2.9325 ns | 144.811 ns | 1.00 | 0.00 | - | - | - | - | |||
Single_Concat | /path/one/two/three | 139.083 ns | 2.5119 ns | 2.2268 ns | 139.028 ns | 0.96 | 0.02 | - | - | - | - | |||
String_Create | /path/one/two/three | 139.504 ns | 2.0265 ns | 1.8956 ns | 139.482 ns | 0.96 | 0.03 | - | - | - | - | |||
UriHelper_BuildRelative | /path/one/two/three | #fragment | 185.283 ns | 3.7939 ns | 4.5163 ns | 184.090 ns | 1.00 | 0.00 | 0.0191 | - | - | 80 B | ||
Single_Concat | /path/one/two/three | #fragment | 172.680 ns | 2.7930 ns | 2.8682 ns | 171.917 ns | 0.93 | 0.03 | 0.0191 | - | - | 80 B | ||
String_Create | /path/one/two/three | #fragment | 181.909 ns | 1.8544 ns | 1.6439 ns | 181.797 ns | 0.98 | 0.03 | 0.0191 | - | - | 80 B | ||
UriHelper_BuildRelative | /path/one/two/three | ?param1=value1¶m2=value2¶m3=value3 | 246.532 ns | 4.9295 ns | 5.2746 ns | 245.078 ns | 1.00 | 0.00 | 0.0343 | - | - | 144 B | ||
Single_Concat | /path/one/two/three | ?param1=value1¶m2=value2¶m3=value3 | 236.133 ns | 2.2584 ns | 1.8859 ns | 236.346 ns | 0.96 | 0.02 | 0.0343 | - | - | 144 B | ||
String_Create | /path/one/two/three | ?param1=value1¶m2=value2¶m3=value3 | 256.853 ns | 3.0590 ns | 2.5544 ns | 256.886 ns | 1.04 | 0.02 | 0.0343 | - | - | 144 B | ||
UriHelper_BuildRelative | /path/one/two/three | ?param1=value1¶m2=value2¶m3=value3 | #fragment | 250.724 ns | 4.9369 ns | 4.8486 ns | 250.070 ns | 1.00 | 0.00 | 0.0401 | - | - | 168 B | |
Single_Concat | /path/one/two/three | ?param1=value1¶m2=value2¶m3=value3 | #fragment | 246.514 ns | 4.1097 ns | 5.4863 ns | 244.180 ns | 0.99 | 0.03 | 0.0401 | - | - | 168 B | |
String_Create | /path/one/two/three | ?param1=value1¶m2=value2¶m3=value3 | #fragment | 262.577 ns | 4.2791 ns | 4.0026 ns | 261.847 ns | 1.05 | 0.03 | 0.0401 | - | - | 168 B | |
UriHelper_BuildRelative | /base-path | 78.592 ns | 1.3297 ns | 1.4780 ns | 78.622 ns | 1.00 | 0.00 | - | - | - | - | |||
Single_Concat | /base-path | 76.572 ns | 1.6192 ns | 2.4236 ns | 75.729 ns | 0.98 | 0.04 | - | - | - | - | |||
String_Create | /base-path | 78.741 ns | 1.2896 ns | 1.1432 ns | 78.702 ns | 1.00 | 0.03 | - | - | - | - | |||
UriHelper_BuildRelative | /base-path | #fragment | 109.622 ns | 1.8629 ns | 1.7426 ns | 109.826 ns | 1.00 | 0.00 | 0.0153 | - | - | 64 B | ||
Single_Concat | /base-path | #fragment | 95.689 ns | 1.2692 ns | 1.1872 ns | 95.927 ns | 0.87 | 0.02 | 0.0153 | - | - | 64 B | ||
String_Create | /base-path | #fragment | 104.016 ns | 0.7687 ns | 0.6814 ns | 104.075 ns | 0.95 | 0.02 | 0.0153 | - | - | 64 B | ||
UriHelper_BuildRelative | /base-path | ?param1=value1¶m2=value2¶m3=value3 | 181.487 ns | 3.3279 ns | 4.7728 ns | 180.207 ns | 1.00 | 0.00 | 0.0305 | - | - | 128 B | ||
Single_Concat | /base-path | ?param1=value1¶m2=value2¶m3=value3 | 169.635 ns | 3.2086 ns | 3.8196 ns | 168.079 ns | 0.93 | 0.04 | 0.0305 | - | - | 128 B | ||
String_Create | /base-path | ?param1=value1¶m2=value2¶m3=value3 | 180.100 ns | 2.3870 ns | 1.9932 ns | 179.820 ns | 0.98 | 0.04 | 0.0305 | - | - | 128 B | ||
UriHelper_BuildRelative | /base-path | ?param1=value1¶m2=value2¶m3=value3 | #fragment | 185.033 ns | 3.4618 ns | 4.2513 ns | 184.494 ns | 1.00 | 0.00 | 0.0343 | - | - | 144 B | |
Single_Concat | /base-path | ?param1=value1¶m2=value2¶m3=value3 | #fragment | 178.615 ns | 3.6554 ns | 8.4720 ns | 174.128 ns | 0.99 | 0.06 | 0.0343 | - | - | 144 B | |
String_Create | /base-path | ?param1=value1¶m2=value2¶m3=value3 | #fragment | 185.893 ns | 3.5433 ns | 2.7663 ns | 185.250 ns | 1.00 | 0.03 | 0.0343 | - | - | 144 B | |
UriHelper_BuildRelative | /base-path | /path/one/two/three | 240.085 ns | 3.2488 ns | 2.8800 ns | 239.380 ns | 1.00 | 0.00 | 0.0191 | - | - | 80 B | ||
Single_Concat | /base-path | /path/one/two/three | 224.022 ns | 2.7651 ns | 2.4512 ns | 223.712 ns | 0.93 | 0.01 | 0.0191 | - | - | 80 B | ||
String_Create | /base-path | /path/one/two/three | 246.030 ns | 4.8594 ns | 4.9903 ns | 244.354 ns | 1.03 | 0.03 | 0.0191 | - | - | 80 B | ||
UriHelper_BuildRelative | /base-path | /path/one/two/three | #fragment | 250.323 ns | 3.2801 ns | 2.9077 ns | 249.957 ns | 1.00 | 0.00 | 0.0439 | - | - | 184 B | |
Single_Concat | /base-path | /path/one/two/three | #fragment | 242.748 ns | 2.3555 ns | 2.0881 ns | 242.596 ns | 0.97 | 0.01 | 0.0248 | - | - | 104 B | |
String_Create | /base-path | /path/one/two/three | #fragment | 257.290 ns | 3.4151 ns | 2.8517 ns | 257.640 ns | 1.03 | 0.02 | 0.0248 | - | - | 104 B | |
UriHelper_BuildRelative | /base-path | /path/one/two/three | ?param1=value1¶m2=value2¶m3=value3 | 330.477 ns | 3.8466 ns | 3.0032 ns | 330.757 ns | 1.00 | 0.00 | 0.0591 | - | - | 248 B | |
Single_Concat | /base-path | /path/one/two/three | ?param1=value1¶m2=value2¶m3=value3 | 317.222 ns | 5.0941 ns | 6.6238 ns | 315.922 ns | 0.96 | 0.02 | 0.0401 | - | - | 168 B | |
String_Create | /base-path | /path/one/two/three | ?param1=value1¶m2=value2¶m3=value3 | 316.704 ns | 5.4259 ns | 5.3290 ns | 315.759 ns | 0.96 | 0.02 | 0.0401 | - | - | 168 B | |
UriHelper_BuildRelative | /base-path | /path/one/two/three | ?param1=value1¶m2=value2¶m3=value3 | #fragment | 340.993 ns | 3.4605 ns | 3.0676 ns | 341.252 ns | 1.00 | 0.00 | 0.0629 | - | - | 264 B |
Single_Concat | /base-path | /path/one/two/three | ?param1=value1¶m2=value2¶m3=value3 | #fragment | 318.045 ns | 5.1664 ns | 4.5799 ns | 317.869 ns | 0.93 | 0.02 | 0.0439 | - | - | 184 B |
String_Create | /base-path | /path/one/two/three | ?param1=value1¶m2=value2¶m3=value3 | #fragment | 337.393 ns | 6.1599 ns | 8.6353 ns | 335.176 ns | 0.99 | 0.03 | 0.0439 | - | - | 184 B |
Code
[MemoryDiagnoser]
public class BuildRelativeBenchmark
{
public IEnumerable<object[]> Data() => TestData.PathBasePathQueryFragment();
[Benchmark(Baseline = true)]
[ArgumentsSource(nameof(Data))]
public string UriHelper_BuildRelative(PathString pathBase, PathString path, QueryString query, FragmentString fragment)
=> UriHelper.BuildRelative(pathBase, path, query, fragment);
[Benchmark]
[ArgumentsSource(nameof(Data))]
public string Single_Concat(PathString pathBase, PathString path, QueryString query, FragmentString fragment)
{
if (pathBase.HasValue || path.HasValue)
{
return pathBase.ToUriComponent() + path.ToUriComponent() + query.ToUriComponent() + fragment.ToUriComponent();
}
else
{
return "/" + query.ToUriComponent() + fragment.ToUriComponent();
}
}
private static readonly SpanAction<char, (string pathBase, string path, string query, string fragment)> InitializeStringAction = new(InitializeString);
[Benchmark]
[ArgumentsSource(nameof(Data))]
public string String_Create(PathString pathBaseString, PathString pathString, QueryString queryString, FragmentString fragmentString)
{
var pathBase = pathBaseString.ToUriComponent();
var path = pathString.ToUriComponent();
var query = queryString.ToUriComponent();
var fragment = fragmentString.ToUriComponent();
var length = pathBase.Length + path.Length + query.Length + fragment.Length;
if (string.IsNullOrEmpty(pathBase) && string.IsNullOrEmpty(path))
{
if (length == 0)
{
return "/";
}
path = "/";
length++;
}
else
{
if (!string.IsNullOrEmpty(pathBase) && pathBase.Length == length)
{
return pathBase;
}
if (path.Length == length)
{
return path;
}
}
return string.Create(length, (pathBase, path, query, fragment), InitializeStringSpanAction);
}
private static void InitializeString(Span<char> buffer, (string pathBase, string path, string query, string fragment) uriParts)
{
var index = 0;
index = Copy(buffer, index, uriParts.pathBase);
index = Copy(buffer, index, uriParts.path);
index = Copy(buffer, index, uriParts.query);
_ = Copy(buffer, index, uriParts.fragment);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static int Copy(Span<char> buffer, int index, string text)
{
if (!string.IsNullOrEmpty(text))
{
var span = text.AsSpan();
span.CopyTo(buffer.Slice(index, span.Length));
return index + span.Length;
}
return index;
}
}
}
public static class TestData
{
private static readonly string[] basePaths = new[] { "", "/base-path", };
private static readonly string[] paths = new[] { "", "/path/one/two/three", };
private static readonly string[] queries = new[] { "", "?param1=value1¶m2=value2¶m3=value3", };
private static readonly string[] fragments = new[] { "", "#fragment", };
public static IEnumerable<object[]> PathBasePathQueryFragment()
{
foreach (var basePath in basePaths)
{
foreach (var path in paths)
{
foreach (var query in queries)
{
foreach (var fragment in fragments)
{
yield return new object[] { new PathString(basePath), new PathString(path), new QueryString(query), new FragmentString(fragment), };
}
}
}
}
}
}