Skip to content

Commit dccf812

Browse files
committed
perf: Update GetLocation implementation
1 parent 5c4a5d8 commit dccf812

File tree

6 files changed

+202
-37
lines changed

6 files changed

+202
-37
lines changed

performance/benchmark/BenchmarkDotNet.Artifacts/results/performance.Descriptions-report-github.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ Job=ShortRun IterationCount=3 LaunchCount=1
1010
WarmupCount=3
1111
1212
```
13-
| Method | Mean | Error | StdDev | Gen0 | Gen1 | Gen2 | Allocated |
14-
|------------- |-------------:|----------------:|-------------:|-----------:|----------:|----------:|-------------:|
15-
| PetStoreYaml | 266.8 μs | 423.33 μs | 23.20 μs | 5.8594 | - | - | 361.38 KB |
16-
| PetStoreJson | 104.9 μs | 38.83 μs | 2.13 μs | 4.3945 | 0.9766 | - | 223.52 KB |
17-
| GHESYaml | 627,326.7 μs | 525,719.23 μs | 28,816.45 μs | 9000.0000 | 8000.0000 | 2000.0000 | 345336.55 KB |
18-
| GHESJson | 256,258.8 μs | 156,676.06 μs | 8,587.94 μs | 4000.0000 | 3000.0000 | 1000.0000 | 206858.38 KB |
19-
| GHESNextYaml | 840,612.7 μs | 1,063,489.38 μs | 58,293.44 μs | 20000.0000 | 9000.0000 | 2000.0000 | 908819.18 KB |
20-
| GHESNextJson | 478,093.3 μs | 98,044.22 μs | 5,374.13 μs | 16000.0000 | 7000.0000 | 1000.0000 | 774015.55 KB |
13+
| Method | Mean | Error | StdDev | Gen0 | Gen1 | Gen2 | Allocated |
14+
|------------- |-------------:|--------------:|-------------:|-----------:|----------:|----------:|-------------:|
15+
| PetStoreYaml | 261.1 μs | 105.89 μs | 5.80 μs | 5.8594 | - | - | 361.38 KB |
16+
| PetStoreJson | 101.9 μs | 48.20 μs | 2.64 μs | 4.3945 | 0.9766 | - | 223.52 KB |
17+
| GHESYaml | 602,932.7 μs | 170,410.86 μs | 9,340.79 μs | 9000.0000 | 8000.0000 | 2000.0000 | 345336.55 KB |
18+
| GHESJson | 254,976.7 μs | 111,875.43 μs | 6,132.27 μs | 4000.0000 | 3000.0000 | 1000.0000 | 206858.06 KB |
19+
| GHESNextYaml | 729,602.0 μs | 357,122.29 μs | 19,575.08 μs | 13000.0000 | 9000.0000 | 2000.0000 | 541566.37 KB |
20+
| GHESNextJson | 378,208.4 μs | 109,458.45 μs | 5,999.79 μs | 8000.0000 | 5000.0000 | 1000.0000 | 406762.41 KB |
Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Method;Job;AnalyzeLaunchVariance;EvaluateOverhead;MaxAbsoluteError;MaxRelativeError;MinInvokeCount;MinIterationTime;OutlierMode;Affinity;EnvironmentVariables;Jit;LargeAddressAware;Platform;PowerPlanMode;Runtime;AllowVeryLargeObjects;Concurrent;CpuGroups;Force;HeapAffinitizeMask;HeapCount;NoAffinitize;RetainVm;Server;Arguments;BuildConfiguration;Clock;EngineFactory;NuGetReferences;Toolchain;IsMutator;InvocationCount;IterationCount;IterationTime;LaunchCount;MaxIterationCount;MaxWarmupIterationCount;MemoryRandomization;MinIterationCount;MinWarmupIterationCount;RunStrategy;UnrollFactor;WarmupCount;Mean;Error;StdDev;Gen0;Gen1;Gen2;Allocated
2-
PetStoreYaml;ShortRun;False;Default;Default;Default;Default;Default;Default;1111111111111111;Empty;RyuJit;Default;X64;8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c;.NET 8.0;False;True;False;True;Default;Default;False;False;False;Default;Default;Default;Default;Default;Default;Default;Default;3;Default;1;Default;Default;Default;Default;Default;Default;16;3;266.8 μs;423.33 μs;23.20 μs;5.8594;0.0000;0.0000;361.38 KB
3-
PetStoreJson;ShortRun;False;Default;Default;Default;Default;Default;Default;1111111111111111;Empty;RyuJit;Default;X64;8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c;.NET 8.0;False;True;False;True;Default;Default;False;False;False;Default;Default;Default;Default;Default;Default;Default;Default;3;Default;1;Default;Default;Default;Default;Default;Default;16;3;104.9 μs;38.83 μs;2.13 μs;4.3945;0.9766;0.0000;223.52 KB
4-
GHESYaml;ShortRun;False;Default;Default;Default;Default;Default;Default;1111111111111111;Empty;RyuJit;Default;X64;8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c;.NET 8.0;False;True;False;True;Default;Default;False;False;False;Default;Default;Default;Default;Default;Default;Default;Default;3;Default;1;Default;Default;Default;Default;Default;Default;16;3;"627,326.7 μs";"525,719.23 μs";"28,816.45 μs";9000.0000;8000.0000;2000.0000;345336.55 KB
5-
GHESJson;ShortRun;False;Default;Default;Default;Default;Default;Default;1111111111111111;Empty;RyuJit;Default;X64;8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c;.NET 8.0;False;True;False;True;Default;Default;False;False;False;Default;Default;Default;Default;Default;Default;Default;Default;3;Default;1;Default;Default;Default;Default;Default;Default;16;3;"256,258.8 μs";"156,676.06 μs";"8,587.94 μs";4000.0000;3000.0000;1000.0000;206858.38 KB
6-
GHESNextYaml;ShortRun;False;Default;Default;Default;Default;Default;Default;1111111111111111;Empty;RyuJit;Default;X64;8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c;.NET 8.0;False;True;False;True;Default;Default;False;False;False;Default;Default;Default;Default;Default;Default;Default;Default;3;Default;1;Default;Default;Default;Default;Default;Default;16;3;"840,612.7 μs";"1,063,489.38 μs";"58,293.44 μs";20000.0000;9000.0000;2000.0000;908819.18 KB
7-
GHESNextJson;ShortRun;False;Default;Default;Default;Default;Default;Default;1111111111111111;Empty;RyuJit;Default;X64;8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c;.NET 8.0;False;True;False;True;Default;Default;False;False;False;Default;Default;Default;Default;Default;Default;Default;Default;3;Default;1;Default;Default;Default;Default;Default;Default;16;3;"478,093.3 μs";"98,044.22 μs";"5,374.13 μs";16000.0000;7000.0000;1000.0000;774015.55 KB
2+
PetStoreYaml;ShortRun;False;Default;Default;Default;Default;Default;Default;1111111111111111;Empty;RyuJit;Default;X64;8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c;.NET 8.0;False;True;False;True;Default;Default;False;False;False;Default;Default;Default;Default;Default;Default;Default;Default;3;Default;1;Default;Default;Default;Default;Default;Default;16;3;261.1 μs;105.89 μs;5.80 μs;5.8594;0.0000;0.0000;361.38 KB
3+
PetStoreJson;ShortRun;False;Default;Default;Default;Default;Default;Default;1111111111111111;Empty;RyuJit;Default;X64;8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c;.NET 8.0;False;True;False;True;Default;Default;False;False;False;Default;Default;Default;Default;Default;Default;Default;Default;3;Default;1;Default;Default;Default;Default;Default;Default;16;3;101.9 μs;48.20 μs;2.64 μs;4.3945;0.9766;0.0000;223.52 KB
4+
GHESYaml;ShortRun;False;Default;Default;Default;Default;Default;Default;1111111111111111;Empty;RyuJit;Default;X64;8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c;.NET 8.0;False;True;False;True;Default;Default;False;False;False;Default;Default;Default;Default;Default;Default;Default;Default;3;Default;1;Default;Default;Default;Default;Default;Default;16;3;"602,932.7 μs";"170,410.86 μs";"9,340.79 μs";9000.0000;8000.0000;2000.0000;345336.55 KB
5+
GHESJson;ShortRun;False;Default;Default;Default;Default;Default;Default;1111111111111111;Empty;RyuJit;Default;X64;8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c;.NET 8.0;False;True;False;True;Default;Default;False;False;False;Default;Default;Default;Default;Default;Default;Default;Default;3;Default;1;Default;Default;Default;Default;Default;Default;16;3;"254,976.7 μs";"111,875.43 μs";"6,132.27 μs";4000.0000;3000.0000;1000.0000;206858.06 KB
6+
GHESNextYaml;ShortRun;False;Default;Default;Default;Default;Default;Default;1111111111111111;Empty;RyuJit;Default;X64;8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c;.NET 8.0;False;True;False;True;Default;Default;False;False;False;Default;Default;Default;Default;Default;Default;Default;Default;3;Default;1;Default;Default;Default;Default;Default;Default;16;3;"729,602.0 μs";"357,122.29 μs";"19,575.08 μs";13000.0000;9000.0000;2000.0000;541566.37 KB
7+
GHESNextJson;ShortRun;False;Default;Default;Default;Default;Default;Default;1111111111111111;Empty;RyuJit;Default;X64;8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c;.NET 8.0;False;True;False;True;Default;Default;False;False;False;Default;Default;Default;Default;Default;Default;Default;Default;3;Default;1;Default;Default;Default;Default;Default;Default;16;3;"378,208.4 μs";"109,458.45 μs";"5,999.79 μs";8000.0000;5000.0000;1000.0000;406762.41 KB

performance/benchmark/BenchmarkDotNet.Artifacts/results/performance.Descriptions-report.html

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<html lang='en'>
33
<head>
44
<meta charset='utf-8' />
5-
<title>performance.Descriptions-20260222-175619</title>
5+
<title>performance.Descriptions-20260223-221211</title>
66

77
<style type="text/css">
88
table { border-collapse: collapse; display: block; width: 100%; overflow: auto; }
@@ -24,14 +24,14 @@
2424
</code></pre>
2525

2626
<table>
27-
<thead><tr><th>Method</th><th>Mean </th><th>Error </th><th>StdDev</th><th>Gen0</th><th>Gen1</th><th>Gen2</th><th>Allocated</th>
27+
<thead><tr><th>Method</th><th>Mean </th><th>Error </th><th>StdDev</th><th>Gen0</th><th>Gen1</th><th>Gen2</th><th>Allocated</th>
2828
</tr>
29-
</thead><tbody><tr><td>PetStoreYaml</td><td>266.8 &mu;s</td><td>423.33 &mu;s</td><td>23.20 &mu;s</td><td>5.8594</td><td>-</td><td>-</td><td>361.38 KB</td>
30-
</tr><tr><td>PetStoreJson</td><td>104.9 &mu;s</td><td>38.83 &mu;s</td><td>2.13 &mu;s</td><td>4.3945</td><td>0.9766</td><td>-</td><td>223.52 KB</td>
31-
</tr><tr><td>GHESYaml</td><td>627,326.7 &mu;s</td><td>525,719.23 &mu;s</td><td>28,816.45 &mu;s</td><td>9000.0000</td><td>8000.0000</td><td>2000.0000</td><td>345336.55 KB</td>
32-
</tr><tr><td>GHESJson</td><td>256,258.8 &mu;s</td><td>156,676.06 &mu;s</td><td>8,587.94 &mu;s</td><td>4000.0000</td><td>3000.0000</td><td>1000.0000</td><td>206858.38 KB</td>
33-
</tr><tr><td>GHESNextYaml</td><td>840,612.7 &mu;s</td><td>1,063,489.38 &mu;s</td><td>58,293.44 &mu;s</td><td>20000.0000</td><td>9000.0000</td><td>2000.0000</td><td>908819.18 KB</td>
34-
</tr><tr><td>GHESNextJson</td><td>478,093.3 &mu;s</td><td>98,044.22 &mu;s</td><td>5,374.13 &mu;s</td><td>16000.0000</td><td>7000.0000</td><td>1000.0000</td><td>774015.55 KB</td>
29+
</thead><tbody><tr><td>PetStoreYaml</td><td>261.1 &mu;s</td><td>105.89 &mu;s</td><td>5.80 &mu;s</td><td>5.8594</td><td>-</td><td>-</td><td>361.38 KB</td>
30+
</tr><tr><td>PetStoreJson</td><td>101.9 &mu;s</td><td>48.20 &mu;s</td><td>2.64 &mu;s</td><td>4.3945</td><td>0.9766</td><td>-</td><td>223.52 KB</td>
31+
</tr><tr><td>GHESYaml</td><td>602,932.7 &mu;s</td><td>170,410.86 &mu;s</td><td>9,340.79 &mu;s</td><td>9000.0000</td><td>8000.0000</td><td>2000.0000</td><td>345336.55 KB</td>
32+
</tr><tr><td>GHESJson</td><td>254,976.7 &mu;s</td><td>111,875.43 &mu;s</td><td>6,132.27 &mu;s</td><td>4000.0000</td><td>3000.0000</td><td>1000.0000</td><td>206858.06 KB</td>
33+
</tr><tr><td>GHESNextYaml</td><td>729,602.0 &mu;s</td><td>357,122.29 &mu;s</td><td>19,575.08 &mu;s</td><td>13000.0000</td><td>9000.0000</td><td>2000.0000</td><td>541566.37 KB</td>
34+
</tr><tr><td>GHESNextJson</td><td>378,208.4 &mu;s</td><td>109,458.45 &mu;s</td><td>5,999.79 &mu;s</td><td>8000.0000</td><td>5000.0000</td><td>1000.0000</td><td>406762.41 KB</td>
3535
</tr></tbody></table>
3636
</body>
3737
</html>

performance/benchmark/BenchmarkDotNet.Artifacts/results/performance.Descriptions-report.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

src/Microsoft.OpenApi/Reader/ParsingContext.cs

Lines changed: 61 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,8 @@
22
// Licensed under the MIT license.
33

44
using System;
5+
using System.Buffers;
56
using System.Collections.Generic;
6-
using System.Linq;
7-
using System.Text;
87
using System.Text.Json.Nodes;
98
using Microsoft.OpenApi.Reader.V2;
109
using Microsoft.OpenApi.Reader.V3;
@@ -25,7 +24,7 @@ public class ParsingContext
2524
/// <summary>
2625
/// Extension parsers
2726
/// </summary>
28-
public Dictionary<string, Func<JsonNode, OpenApiSpecVersion, IOpenApiExtension>>? ExtensionParsers { get; set; } =
27+
public Dictionary<string, Func<JsonNode, OpenApiSpecVersion, IOpenApiExtension>>? ExtensionParsers { get; set; } =
2928
new();
3029

3130
internal RootNode? RootNode { get; set; }
@@ -180,28 +179,76 @@ public string GetLocation()
180179
return "#/";
181180
}
182181

183-
var sb = new StringBuilder("#/");
182+
// Calculate required buffer size upfront to avoid resizing
183+
int totalLength = 2; // "#/"
184184
var segments = _currentLocation.ToArray();
185-
185+
186186
for (int i = segments.Length - 1; i >= 0; i--)
187187
{
188188
var segment = segments[i];
189-
190-
// Escape ~ and / per RFC 6901
191-
if (segment.Contains("~") || segment.Contains("/"))
189+
totalLength += segment.Length;
190+
191+
// Account for escape character expansions (~ -> ~0, / -> ~1)
192+
foreach (char c in segment)
192193
{
193-
segment = segment.Replace("~", "~0").Replace("/", "~1");
194+
if (c == '~' || c == '/')
195+
{
196+
totalLength++; // Each escape adds one additional character
197+
}
194198
}
195-
196-
sb.Append(segment);
197-
199+
198200
if (i > 0)
199201
{
200-
sb.Append('/');
202+
totalLength++; // Account for the '/' separator
201203
}
202204
}
203205

204-
return sb.ToString();
206+
char[] rentedBuffer = ArrayPool<char>.Shared.Rent(totalLength);
207+
208+
try
209+
{
210+
int position = 0;
211+
rentedBuffer[position++] = '#';
212+
rentedBuffer[position++] = '/';
213+
214+
for (int i = segments.Length - 1; i >= 0; i--)
215+
{
216+
var segment = segments[i];
217+
218+
foreach (char c in segment)
219+
{
220+
if (c == '~')
221+
{
222+
rentedBuffer[position++] = '~';
223+
rentedBuffer[position++] = '0';
224+
}
225+
else if (c == '/')
226+
{
227+
rentedBuffer[position++] = '~';
228+
rentedBuffer[position++] = '1';
229+
}
230+
else
231+
{
232+
rentedBuffer[position++] = c;
233+
}
234+
}
235+
236+
if (i > 0)
237+
{
238+
rentedBuffer[position++] = '/';
239+
}
240+
}
241+
242+
#if NETSTANDARD2_0
243+
return new string(rentedBuffer, 0, position);
244+
#else
245+
return new string(rentedBuffer.AsSpan(0, position));
246+
#endif
247+
}
248+
finally
249+
{
250+
ArrayPool<char>.Shared.Return(rentedBuffer, clearArray: true);
251+
}
205252
}
206253

207254
/// <summary>
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT license.
3+
4+
using Microsoft.OpenApi.Reader;
5+
using Xunit;
6+
7+
namespace Microsoft.OpenApi.Tests
8+
{
9+
public class ParsingContextTests
10+
{
11+
private static ParsingContext CreateContext() => new(new OpenApiDiagnostic());
12+
13+
[Fact]
14+
public void GetLocation_NoSegments_ReturnsRoot()
15+
{
16+
var context = CreateContext();
17+
18+
var location = context.GetLocation();
19+
20+
Assert.Equal("#/", location);
21+
}
22+
23+
[Fact]
24+
public void GetLocation_SingleSegment_ReturnsSegment()
25+
{
26+
var context = CreateContext();
27+
context.StartObject("paths");
28+
29+
var location = context.GetLocation();
30+
31+
Assert.Equal("#/paths", location);
32+
}
33+
34+
[Fact]
35+
public void GetLocation_MultipleSegments_ReturnsCombinedPath()
36+
{
37+
var context = CreateContext();
38+
context.StartObject("paths");
39+
context.StartObject("foo");
40+
context.StartObject("get");
41+
42+
var location = context.GetLocation();
43+
44+
Assert.Equal("#/paths/foo/get", location);
45+
}
46+
47+
[Fact]
48+
public void GetLocation_AfterEndObject_RemovesLastSegment()
49+
{
50+
var context = CreateContext();
51+
context.StartObject("paths");
52+
context.StartObject("foo");
53+
context.EndObject();
54+
55+
var location = context.GetLocation();
56+
57+
Assert.Equal("#/paths", location);
58+
}
59+
60+
[Fact]
61+
public void GetLocation_AfterAllEndObjects_ReturnsRoot()
62+
{
63+
var context = CreateContext();
64+
context.StartObject("paths");
65+
context.EndObject();
66+
67+
var location = context.GetLocation();
68+
69+
Assert.Equal("#/", location);
70+
}
71+
72+
[Fact]
73+
public void GetLocation_SegmentWithTilde_EscapesTildeAsPerRfc6901()
74+
{
75+
var context = CreateContext();
76+
context.StartObject("foo~bar");
77+
78+
var location = context.GetLocation();
79+
80+
Assert.Equal("#/foo~0bar", location);
81+
}
82+
83+
[Fact]
84+
public void GetLocation_SegmentWithSlash_EscapesSlashAsPerRfc6901()
85+
{
86+
var context = CreateContext();
87+
context.StartObject("application/json");
88+
89+
var location = context.GetLocation();
90+
91+
Assert.Equal("#/application~1json", location);
92+
}
93+
94+
[Fact]
95+
public void GetLocation_SegmentWithTildeAndSlash_EscapesBothAsPerRfc6901()
96+
{
97+
var context = CreateContext();
98+
context.StartObject("a~b/c");
99+
100+
var location = context.GetLocation();
101+
102+
Assert.Equal("#/a~0b~1c", location);
103+
}
104+
105+
[Fact]
106+
public void GetLocation_MultipleSegmentsWithSpecialChars_EscapesEachSegment()
107+
{
108+
var context = CreateContext();
109+
context.StartObject("content");
110+
context.StartObject("application/json");
111+
context.StartObject("schema");
112+
113+
var location = context.GetLocation();
114+
115+
Assert.Equal("#/content/application~1json/schema", location);
116+
}
117+
}
118+
}

0 commit comments

Comments
 (0)