Skip to content

Improve OpenApiWalker performance - #2470

Merged
Vincent Biret (baywet) merged 9 commits into
microsoft:mainfrom
martincostello:improve-OpenApiWalker-perf
Aug 20, 2025
Merged

Improve OpenApiWalker performance#2470
Vincent Biret (baywet) merged 9 commits into
microsoft:mainfrom
martincostello:improve-OpenApiWalker-perf

Conversation

@martincostello

Copy link
Copy Markdown
Contributor

Benchmark results: #2459 (comment)

Given that the hardware between my local setup and GitHub Actions is quite different, I suggest merging this PR once happy with the code changes with the failing benchmark comparison, then downloading the results from the run in main and opening a new PR to use those files to replace the contents of performance/benchmark/BenchmarkDotNet.Artifacts/results and update the baseline.

Changes

  • Avoid allocations from lambda closures.
  • Avoid allocations from context strings.
  • Avoid allocations from copying arrays.
  • Remove redundant null checks.
  • Ignore BenchmarkDotNet profiler files.
  • Ignore Visual Studio profiler session files.
  • Publish benchmark results to GitHub Actions workflow artifacts.

Publish benchmark results to GitHub Actions workflow artifacts.
- Ignore BenchmarkDotNet profiler files.
- Ignore Visual Studio profiler session files.
- Avoid allocations from lambda closures.
- Avoid allocations from context strings.
- Avoid allocations from copying arrays.
- Remove redundant null checks.
CopilotAI review requested due to automatic review settings August 20, 2025 09:10
Comment threadsrc/Microsoft.OpenApi/Services/OpenApiWalker.cs

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR optimizes the performance of the OpenApiWalker class by eliminating allocations from lambda closures, string concatenations, and array copying operations. The changes focus on replacing the existing Walk(context, action) pattern with more efficient WalkItem and WalkDictionary helper methods that avoid capturing variables in closures.

Key changes include:

  • Replace lambda closures with direct method calls to avoid allocation overhead
  • Add specialized WalkItem and WalkDictionary helper methods for efficient traversal
  • Implement optimized iteration patterns for collections to avoid array copying
  • Publish benchmark results as GitHub Actions artifacts for performance tracking

Reviewed Changes

Copilot reviewed 2 out of 3 changed files in this pull request and generated 4 comments.

FileDescription
src/Microsoft.OpenApi/Services/OpenApiWalker.csCore performance optimizations replacing lambda-based traversal with direct method calls
.github/workflows/ci-cd.ymlAdd artifact publishing for benchmark results

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment threadsrc/Microsoft.OpenApi/Services/OpenApiWalker.cs Outdated
Comment threadsrc/Microsoft.OpenApi/Services/OpenApiWalker.cs Outdated
Comment threadsrc/Microsoft.OpenApi/Services/OpenApiWalker.cs
Comment threadsrc/Microsoft.OpenApi/Services/OpenApiWalker.cs Outdated
@martincostello

Copy link
Copy Markdown
ContributorAuthor

FYI I ran each of the benchmarks using the Visual Studio profiler to get the allocations, and these are the top types:

image

Even more refactoring of the walker in a future PR to remove the delegates would probably significantly reduce the allocations:

image

@martincostello

Copy link
Copy Markdown
ContributorAuthor

Some of these might be new from the refactoring actually, so I'll see if I can shave it down more here...

Make all delegates static.
@martincostello

Copy link
Copy Markdown
ContributorAuthor

With the latest changes:

MethodMean BeforeMean AfterRatioMemory BeforeMemory AfterRatio
PetStoreYaml265.6 μs293.3 μs1.10387.12 KB386.91 KB0.99
PetStoreJson106.6 μs134.4 μs1.26249.26 KB249.06 KB0.99
GHESYaml774,833.9 μs788,176.8 μs1.01400088.73 KB384130.43 KB0.96
GHESJson364,114.2 μs368,179.1 μs1.01261558.87 KB262008.28 KB1.00

BenchmarkDotNet v0.15.2, Windows 11 (10.0.26100.4946/24H2/2024Update/HudsonValley)
13th Gen Intel Core i7-13700H 2.90GHz, 1 CPU, 20 logical and 14 physical cores
.NET SDK 8.0.413
[Host] : .NET 8.0.19 (8.0.1925.36514), X64 RyuJIT AVX2
ShortRun : .NET 8.0.19 (8.0.1925.36514), X64 RyuJIT AVX2
Job=ShortRun IterationCount=3 LaunchCount=1 WarmupCount=3 
MethodMeanErrorStdDevGen0Gen1Gen2Allocated
PetStoreYaml293.3 μs23.48 μs1.29 μs31.25005.8594-386.91 KB
PetStoreJson134.4 μs49.91 μs2.74 μs19.53134.8828-249.06 KB
GHESYaml788,176.8 μs246,902.17 μs13,533.54 μs35000.000019000.00004000.0000384130.43 KB
GHESJson368,179.1 μs23,852.48 μs1,307.43 μs20000.000011000.00002000.0000262008.28 KB

@martincostello

Copy link
Copy Markdown
ContributorAuthor

Action<T> has dropped off top of the allocations now:

image

@martincostello
Martin Costello (martincostello) marked this pull request as ready for review August 20, 2025 12:25
Comment threadsrc/Microsoft.OpenApi/Services/OpenApiWalker.cs Fixed
Comment threadsrc/Microsoft.OpenApi/Services/OpenApiWalker.cs Fixed
Resolve two CodeQL warnings.

@baywetVincent Biret (baywet) left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is some great refactoring that was overdue here! Thanks for taking the time. I have left a few comments.

And yes... it's not the first time I see the action context capture using a lot of memory at scale...

Comment threadsrc/Microsoft.OpenApi/Services/OpenApiWalker.cs Outdated
Comment threadsrc/Microsoft.OpenApi/Services/OpenApiWalker.cs Outdated
@martincostello

Martin Costello (martincostello) commented Aug 20, 2025

Copy link
Copy Markdown
ContributorAuthor

Here's all the relevant benchmark data taken from the CI logs.

TL;DR:

10b46b9

MethodMeanErrorStdDevGen0Gen1Gen2Allocated
PetStoreYaml502.9 us85.92 us4.71 us23.43753.9063-387.12 KB
PetStoreJson215.0 us10.97 us0.60 us14.64843.9063-248.97 KB
GHESYaml1,063,967.0 us51,235.65 us2,808.40 us27000.000020000.00003000.0000400086.95 KB
GHESJson509,285.0 us133,784.60 us7,333.19 us17000.000010000.00002000.0000261557.86 KB

90b3966

MethodMeanErrorStdDevGen0Gen1Gen2Allocated
PetStoreYaml553.4 us19.28 us1.06 us26.36725.8594-445.71 KB
PetStoreJson276.7 us55.86 us3.06 us17.57813.9063-307.56 KB
GHESYaml1,055,962.2 us291,474.86 us15,976.72 us28000.000020000.00003000.0000422172.41 KB
GHESJson509,499.6 us119,766.91 us6,564.83 us18000.000010000.00002000.0000283643.26 KB

This PR

MethodMeanErrorStdDevGen0Gen1Gen2Allocated
PetStoreYaml511.4 us68.15 us3.74 us23.43753.9063-386.9 KB
PetStoreJson233.9 us20.54 us1.13 us13.67193.9063-248.75 KB
GHESYaml1,063,226.7 us317,042.02 us17,378.14 us26000.000020000.00003000.0000384128.47 KB
GHESJson472,314.9 us54,982.85 us3,013.80 us16000.00009000.00002000.0000245598.41 KB

@martincostello

Martin Costello (martincostello) commented Aug 20, 2025

Copy link
Copy Markdown
ContributorAuthor

With the factoring suggestions the numbers get slightly worse than without, but it's probably within the realms of noise.

MethodMeanErrorStdDevGen0Gen1Gen2Allocated
PetStoreYaml533.9 us467.5 us25.62 us23.43753.9063-387.26 KB
PetStoreJson243.2 us251.9 us13.81 us13.67191.9531-249.1 KB
GHESYaml1,044,990.0 us80,027.6 us4,386.59 us26000.000020000.00003000.0000384490.94 KB
GHESJson481,887.0 us222,646.4 us12,204.00 us16000.00009000.00002000.0000245957.72 KB

- Create helpers to reduce code duplication.
- Remove unused method.
@martincostello

Copy link
Copy Markdown
ContributorAuthor

Vincent Biret (@baywet) Assuming you're otherwise happy with the PR and the changes to the perf numbers, do you want me to take the files from the latest CI run on this PR and check them in as the updated baseline (which should then make the PR green on the next run)?

Comment threadsrc/Microsoft.OpenApi/Services/OpenApiWalker.cs Outdated
Create common implementation to walk `OpenApiTag` and `OpenApiTagReference`.
Comment threadsrc/Microsoft.OpenApi/Services/OpenApiWalker.cs Outdated
Remove redundant comment.

@baywetVincent Biret (baywet) left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for making the changes!

@martincostello

Copy link
Copy Markdown
ContributorAuthor

Latest numbers:

MethodMeanErrorStdDevGen0Gen1Gen2Allocated
PetStoreYaml529.5 us62.50 us3.43 us23.43753.9063-387.26 KB
PetStoreJson240.8 us15.69 us0.86 us13.67191.9531-249.1 KB
GHESYaml1,097,576.6 us100,584.42 us5,513.37 us26000.000020000.00003000.0000384492.38 KB
GHESJson516,328.2 us87,964.22 us4,821.62 us16000.00009000.00002000.0000245957.5 KB

Would you like me to commit the results from that build into this PR before merge?

@baywet

Copy link
Copy Markdown
Member

Martin Costello (@martincostello) yes please go ahead!

@baywet
Vincent Biret (baywet) merged commit a007c03 into microsoft:mainAug 20, 2025
9 checks passed
@martincostello
Martin Costello (martincostello) deleted the improve-OpenApiWalker-perf branch August 20, 2025 15:16
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@martincostello@baywet@github-advanced-security