Method | pathName | Mean | Error | StdDev | Gen0 | Allocated |
---|---|---|---|---|---|---|
WithStringBuilder | longPath | 274.86 ns | 126.345 ns | 6.925 ns | 0.2856 | 1232 B |
WithJoin | longPath | 138.62 ns | 29.393 ns | 1.611 ns | 0.1056 | 456 B |
WithJoinAndArrayPool | longPath | 178.99 ns | 18.947 ns | 1.039 ns | 0.0834 | 360 B |
WithCharArray | longPath | 198.61 ns | 32.978 ns | 1.808 ns | 0.1669 | 720 B |
WithNoTempAllocCharArray | longPath | 169.25 ns | 25.699 ns | 1.409 ns | 0.0834 | 360 B |
WithStringBuilder | shortPath | 58.52 ns | 146.921 ns | 8.053 ns | 0.0352 | 152 B |
WithJoin | shortPath | 55.31 ns | 34.871 ns | 1.911 ns | 0.0222 | 96 B |
WithJoinAndArrayPool | shortPath | 80.61 ns | 28.241 ns | 1.548 ns | 0.0111 | 48 B |
WithCharArray | shortPath | 69.61 ns | 12.681 ns | 0.695 ns | 0.0241 | 104 B |
WithNoTempAllocCharArray | shortPath | 59.23 ns | 3.982 ns | 0.218 ns | 0.0111 | 48 B |
WithStringBuilder | verLongPath | 565.84 ns | 20.285 ns | 1.112 ns | 0.5779 | 2496 B |
WithJoin | verLongPath | 412.97 ns | 214.625 ns | 11.764 ns | 0.2966 | 1280 B |
WithJoinAndArrayPool | verLongPath | 447.89 ns | 49.392 ns | 2.707 ns | 0.2408 | 1040 B |
WithCharArray | verLongPath | 586.33 ns | 327.525 ns | 17.953 ns | 0.4816 | 2080 B |
WithNoTempAllocCharArray | verLongPath | 489.28 ns | 108.696 ns | 5.958 ns | 0.2403 | 1040 B |
Created
December 1, 2023 13:55
-
-
Save habbes/54f9fc9c28683e13d87db479fd829281 to your computer and use it in GitHub Desktop.
Testing different implementations of generating a path string from a collection of segments
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Buffers; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Text; | |
using System.Threading.Tasks; | |
namespace SegmentsPathString | |
{ | |
internal static class PathHelpers | |
{ | |
public static string GetPathStringWithStringBuilder(this SamplePath path) | |
{ | |
StringBuilder builder = new StringBuilder(); | |
int index = 0; | |
while (index < path.Segments.Count) | |
{ | |
if (index != 0) | |
{ | |
builder.Append('/'); | |
} | |
builder.Append(path.Segments[index].Name); | |
index++; | |
} | |
return builder.ToString(); | |
} | |
public static string GetPathStringWithJoin(this SamplePath path) | |
{ | |
string[] segments = new string[path.Segments.Count]; | |
for (int i = 0; i < path.Segments.Count; i++) | |
{ | |
segments[i] = path.Segments[i].Name; | |
} | |
return string.Join("/", segments); | |
} | |
public static string GetPathStringWithJoinAndArrayPool(this SamplePath path) | |
{ | |
string[] segments = ArrayPool<string>.Shared.Rent(path.Segments.Count); | |
for (int i = 0; i < path.Segments.Count; i++) | |
{ | |
segments[i] = path.Segments[i].Name; | |
} | |
string result = string.Join("/", segments, 0, path.Segments.Count); | |
ArrayPool<string>.Shared.Return(segments); | |
return result; | |
} | |
public static string GetPathStringWithCharArray(this SamplePath path) | |
{ | |
int length = 0; | |
for (int i = 0; i < path.Segments.Count; i++) | |
{ | |
if (i != 0) | |
{ | |
length++; // for the separator | |
} | |
length += path.Segments[i].Name.Length; | |
} | |
char[] pathArray = new char[length]; | |
int pathIndex = 0; | |
for (int i = 0; i < path.Segments.Count ; i++) | |
{ | |
if (i != 0) | |
{ | |
pathArray[pathIndex++] = '/'; | |
} | |
string segment = path.Segments[i].Name; | |
segment.CopyTo(0, pathArray, pathIndex, segment.Length); | |
pathIndex += segment.Length; | |
} | |
return new string(pathArray); | |
} | |
public static string GetPathStringWithCharArrayAndPooling(this SamplePath path) | |
{ | |
int length = 0; | |
for (int i = 0; i < path.Segments.Count; i++) | |
{ | |
if (i != 0) | |
{ | |
length++; // for the separator | |
} | |
length += path.Segments[i].Name.Length; | |
} | |
char[] pathArray = null; | |
Span<char> buffer = length < 256 ? | |
stackalloc char[length] : pathArray = ArrayPool<char>.Shared.Rent(length); | |
int pathIndex = 0; | |
for (int i = 0; i < path.Segments.Count; i++) | |
{ | |
if (i != 0) | |
{ | |
buffer[pathIndex++] = '/'; | |
} | |
string segment = path.Segments[i].Name; | |
segment.CopyTo(buffer.Slice(pathIndex, segment.Length)); | |
pathIndex += segment.Length; | |
} | |
string result = new string(buffer.Slice(0, length)); | |
if (pathArray != null) | |
{ | |
ArrayPool<char>.Shared.Return(pathArray); | |
} | |
return result; | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using BenchmarkDotNet.Attributes; | |
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Text; | |
using System.Threading.Tasks; | |
namespace SegmentsPathString | |
{ | |
[MemoryDiagnoser] | |
[ShortRunJob] | |
public class PathStringBenchmarks | |
{ | |
private static Dictionary<string, SamplePath> Paths = new Dictionary<string, SamplePath> | |
{ | |
{ | |
"shortPath", | |
new SamplePath(new[] | |
{ | |
new PathSegment("one"), | |
new PathSegment("two"), | |
new PathSegment("three") | |
}) | |
}, | |
{ | |
"longPath", | |
new SamplePath(new[] | |
{ | |
new PathSegment("oneoneone"), | |
new PathSegment("twotwotwo"), | |
new PathSegment("threethreethree"), | |
new PathSegment("oneoneoneone"), | |
new PathSegment("twotwotwotwotwo"), | |
new PathSegment("threethreethreethree"), | |
new PathSegment("oneoneoneoneoneone"), | |
new PathSegment("twotwotwotwotwotwotwotwotwo"), | |
new PathSegment("threethreethreethreethreethreethree"), | |
}) | |
}, | |
{ | |
"verLongPath", | |
new SamplePath(new[] | |
{ | |
new PathSegment("oneoneone"), | |
new PathSegment("twotwotwo"), | |
new PathSegment("threethreethree"), | |
new PathSegment("oneoneoneone"), | |
new PathSegment("twotwotwotwotwo"), | |
new PathSegment("threethreethreethree"), | |
new PathSegment("oneoneoneoneoneone"), | |
new PathSegment("twotwotwotwotwotwotwotwotwo"), | |
new PathSegment("threethreethreethreethreethreethree"), | |
new PathSegment("oneoneone"), | |
new PathSegment("twotwotwo"), | |
new PathSegment("threethreethree"), | |
new PathSegment("oneoneoneone"), | |
new PathSegment("twotwotwotwotwo"), | |
new PathSegment("threethreethreethree"), | |
new PathSegment("oneoneoneoneoneone"), | |
new PathSegment("twotwotwotwotwotwotwotwotwo"), | |
new PathSegment("threethreethreethreethreethreethree"), | |
new PathSegment("oneoneone"), | |
new PathSegment("twotwotwo"), | |
new PathSegment("threethreethree"), | |
new PathSegment("oneoneoneone"), | |
new PathSegment("twotwotwotwotwo"), | |
new PathSegment("threethreethreethree"), | |
new PathSegment("oneoneoneoneoneone"), | |
new PathSegment("twotwotwotwotwotwotwotwotwo"), | |
new PathSegment("threethreethreethreethreethreethree"), | |
}) | |
} | |
}; | |
[ParamsSource(nameof(GetPathNames))] | |
public string pathName; | |
public IEnumerable<string> GetPathNames() => Paths.Keys; | |
private SamplePath path; | |
[GlobalSetup] | |
public void Setup() | |
{ | |
path = Paths[pathName]; | |
} | |
[Benchmark] | |
public string WithStringBuilder() => path.GetPathStringWithStringBuilder(); | |
[Benchmark] | |
public string WithJoin() => path.GetPathStringWithJoin(); | |
[Benchmark] | |
public string WithJoinAndArrayPool() => path.GetPathStringWithJoinAndArrayPool(); | |
[Benchmark] | |
public string WithCharArray() => path.GetPathStringWithCharArray(); | |
[Benchmark] | |
public string WithNoTempAllocCharArray() => path.GetPathStringWithCharArrayAndPooling(); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// See https://aka.ms/new-console-template for more information | |
using BenchmarkDotNet.Running; | |
using SegmentsPathString; | |
BenchmarkRunner.Run(typeof(PathStringBenchmarks)); | |
//RunTests(); | |
void RunTests() | |
{ | |
try | |
{ | |
Test( | |
("StringBuilder", PathHelpers.GetPathStringWithStringBuilder), | |
("Join", PathHelpers.GetPathStringWithJoin), | |
("JoinWithArrayPool", PathHelpers.GetPathStringWithJoinAndArrayPool), | |
("CharArray", PathHelpers.GetPathStringWithCharArray), | |
("CharArrayAndPooling", PathHelpers.GetPathStringWithCharArrayAndPooling) | |
); | |
} | |
catch | |
{ | |
throw; | |
} | |
} | |
void Test(params (string name, Func<SamplePath, string> method)[] funcs) | |
{ | |
SamplePath path = new SamplePath(new[] | |
{ | |
new PathSegment("oneoneone"), | |
new PathSegment("twotwotwo"), | |
new PathSegment("threethreethree") | |
}); | |
string expected = "oneoneone/twotwotwo/threethreethree"; | |
foreach (var (name, method) in funcs) | |
{ | |
string actual = method(path); | |
if (actual != expected) | |
{ | |
throw new Exception($"Test failed for method {name}. Expected '{expected}' but got '{actual}'"); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Text; | |
using System.Threading.Tasks; | |
namespace SegmentsPathString; | |
internal class SamplePath | |
{ | |
public SamplePath(IEnumerable<PathSegment> segments) | |
{ | |
Segments = segments.ToList(); | |
} | |
public IReadOnlyList<PathSegment> Segments { get; private set; } | |
} | |
class PathSegment | |
{ | |
public PathSegment(string name) | |
{ | |
this.Name = name; | |
} | |
public string Name { get; set; } | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment