Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ServerGarbageCollection>true</ServerGarbageCollection>
<LangVersion>Preview</LangVersion>
<AssemblyName>HotChocolate.Execution.Abstractions.Benchmarks</AssemblyName>
<RootNamespace>HotChocolate.Execution.Abstractions.Benchmarks</RootNamespace>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="BenchmarkDotNet" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\Execution.Abstractions\HotChocolate.Execution.Abstractions.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;

namespace HotChocolate.Execution.Abstractions.Benchmarks;

[MemoryDiagnoser]
[ShortRunJob(RuntimeMoniker.Net10_0)]
public class PathBenchmark
{
private Path _depth5 = null!;
private Path _depth10 = null!;
private Path _depth20 = null!;
private Path _depth50 = null!;

[GlobalSetup]
public void Setup()
{
_depth5 = BuildPath(5);
_depth10 = BuildPath(10);
_depth20 = BuildPath(20);
_depth50 = BuildPath(50);
}

private static Path BuildPath(int depth)
{
var path = Path.Root;
for (var i = 0; i < depth; i++)
{
path = path.Append("field" + i);
if (i % 3 == 2)
{
path = path.Append(i);
}
}
return path;
}

// --- Print benchmarks ---

[Benchmark]
public string Print_Depth5() => _depth5.Print();

[Benchmark]
public string Print_Depth10() => _depth10.Print();

[Benchmark]
public string Print_Depth20() => _depth20.Print();

[Benchmark]
public string Print_Depth50() => _depth50.Print();

// --- ToList benchmarks ---

[Benchmark]
public IReadOnlyList<object> ToList_Depth5() => _depth5.ToList();

[Benchmark]
public IReadOnlyList<object> ToList_Depth10() => _depth10.ToList();

[Benchmark]
public IReadOnlyList<object> ToList_Depth20() => _depth20.ToList();

[Benchmark]
public IReadOnlyList<object> ToList_Depth50() => _depth50.ToList();

// --- EnumerateSegments benchmarks ---

[Benchmark]
public int EnumerateSegments_Depth10()
{
var count = 0;
foreach (var _ in _depth10.EnumerateSegments())
{
count++;
}
return count;
}

[Benchmark]
public int EnumerateSegments_Depth50()
{
var count = 0;
foreach (var _ in _depth50.EnumerateSegments())
{
count++;
}
return count;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
using BenchmarkDotNet.Running;
using HotChocolate.Execution.Abstractions.Benchmarks;

BenchmarkSwitcher.FromAssembly(typeof(PathBenchmark).Assembly).Run(args);
101 changes: 77 additions & 24 deletions src/HotChocolate/Core/src/Execution.Abstractions/Path.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
using System.Text;

namespace HotChocolate;

/// <summary>
Expand Down Expand Up @@ -136,23 +134,20 @@ public string Print()
return "/";
}

var sb = new StringBuilder();
// On first pass we calculate the total length
var totalLength = 0;
var current = this;

while (current is not RootPathSegment)
{
switch (current)
{
case IndexerPathSegment indexer:
var numberValue = indexer.Index.ToString();
sb.Insert(0, '[');
sb.Insert(1, numberValue);
sb.Insert(1 + numberValue.Length, ']');
totalLength += 2 + CountDigits(indexer.Index); // '[' + digits + ']'
break;

case NamePathSegment name:
sb.Insert(0, '/');
sb.Insert(1, name.Name);
totalLength += 1 + name.Name.Length; // '/' + name
break;

default:
Expand All @@ -162,7 +157,60 @@ public string Print()
current = current.Parent;
}

return sb.ToString();
// On second pass we fill from right to left using string.Create
return string.Create(totalLength, this, static (span, path) =>
{
var pos = span.Length;
var current = path;

while (current is not RootPathSegment)
{
switch (current)
{
case IndexerPathSegment indexer:
span[--pos] = ']';
var idx = indexer.Index;
if (idx == 0)
{
span[--pos] = '0';
}
else
{
while (idx > 0)
{
span[--pos] = (char)('0' + (idx % 10));
idx /= 10;
}
}
span[--pos] = '[';
break;

case NamePathSegment name:
pos -= name.Name.Length;
name.Name.AsSpan().CopyTo(span[pos..]);
span[--pos] = '/';
break;
}

current = current.Parent;
}
});

static int CountDigits(int n)
{
if (n == 0)
{
return 1;
}

var count = 0;
while (n > 0)
{
count++;
n /= 10;
}
return count;
}
}

/// <summary>
Expand All @@ -178,19 +226,20 @@ public IReadOnlyList<object> ToList()
return Array.Empty<object>();
}

var stack = new List<object>();
var result = new object[Length];
var current = this;
var i = Length - 1;

while (!current.IsRoot)
{
switch (current)
{
case IndexerPathSegment indexer:
stack.Insert(0, indexer.Index);
result[i--] = indexer.Index;
break;

case NamePathSegment name:
stack.Insert(0, name.Name);
result[i--] = name.Name;
break;

default:
Expand All @@ -200,7 +249,7 @@ public IReadOnlyList<object> ToList()
current = current.Parent;
}

return stack;
return result;
}

/// <summary>
Expand Down Expand Up @@ -247,26 +296,31 @@ public void ToList(Span<object> path)
}

public IEnumerable<Path> EnumerateSegments()
=> EnumerateSegmentsBackwards().Reverse();

private IEnumerable<Path> EnumerateSegmentsBackwards()
{
if (IsRoot)
{
yield break;
return [];
}

var segments = new Path[Length];
var current = this;
var i = Length - 1;

while (!current.IsRoot)
{
yield return current;
segments[i--] = current;
current = current.Parent;
}

return segments;
}

/// <summary>Returns a string that represents the current <see cref="Path"/>.</summary>
/// <returns>A string that represents the current <see cref="Path"/>.</returns>
/// <summary>
/// Returns a string that represents the current <see cref="Path"/>.
/// </summary>
/// <returns>
/// A string that represents the current <see cref="Path"/>.
/// </returns>
public override string ToString() => Print();

public virtual bool Equals(Path? other)
Expand Down Expand Up @@ -323,7 +377,7 @@ public int CompareTo(Path? other)
return 1;
}

// 1. Align to the same depth
// First we align the paths to the the same depth
var a = this;
var b = other;

Expand All @@ -339,14 +393,13 @@ public int CompareTo(Path? other)
b = b.Skip(lenB - lenA);
}

// 2. Walk aligned segments from root to leaf
// Then we walk aligned segments from root to leaf
var cmp = CompareFromRoot(a, b);
if (cmp != 0)
{
return cmp;
}

// 3. Same segments → shorter path wins
return Length.CompareTo(other.Length);
}

Expand Down
Loading