Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve formatting performance #8243

Merged
merged 10 commits into from
Feb 10, 2023

Conversation

DustinCampbell
Copy link
Member

@DustinCampbell DustinCampbell commented Feb 8, 2023

The "large file" formatting test that I recently checked in is slow - really slow.

image

That's far too slow for a file that's only ~40 KB in size -- and that's running on Release bits.

Firing up PerfView revealed that the bulk of the time is spent in an internal extension method in the compiler: LegacySpanExtensions.PreviousSpan(...), and it's not hard to see why. This method causes the entire subtree of the target SyntaxNode to be realized by calling LastOrDefault(...) on the result of LegacySpanExtensions.FlattenSpans(...). Even worse, much of that subtree is represented by nodes that are created each time MarkupStartTagSyntax.Children and MarkupEndTagSyntax.Children are called. I've made the following changes, which greatly improve formatting performance -- especially on large inputs.

Here is a summary of the changes:

  • All of the IsXXXSpanKind(...) extension methods now use hash sets rather than arrays for look up.
  • IsSpanKind(...) no longer calls all of the other IsXXXSpanKind(...) methods. Instead, it uses its own hash set for look up that is unioned with the others.
  • The results of MarkupStartTagSyntax.Children, MarkupEndTagSyntax.Children, MarkupTagHelperStartTagSyntax.Children and MarkupTagHelperEndTagSyntax.Children are now cached on their red nodes to avoid recomputing them (and allocating new nodes!) on every access.
  • NextSpan and PreviousSpan now cache their results in a ConditionalWeakTable.
  • PreviousSpan calls a private FlattenSpansInReverse(...) method that is written to be more efficient than calling FlattenSpans.LastOrDefault(...).
  • LocateOwner no longer boxes struct-based collections before iterating them. This also allows each iteration to use each collection's struct-based enumerator.
  • I went ahead and added nullability annotations and cleaned up a bit as well.

With these changes, the test runs much faster. It's now much more realistic.

image

Benchmarks

The formatting benchmarks are also improved, especially on larger inputs and bigger jobs. The improvements are as much as 50% on the final rows in the tables below.

RazorCSharpFormattingBenchmark (main)

BenchmarkDotNet=v0.13.2.2052-nightly, OS=Windows 11 (10.0.25290.1000)
Intel Core i7-8700 CPU 3.20GHz (Coffee Lake), 1 CPU, 12 logical and 6 physical cores
.NET SDK=7.0.102
  [Host]     : .NET 7.0.3 (7.0.323.6910), X64 RyuJIT AVX2
  Job-HHIMXG : .NET 7.0.3 (7.0.323.6910), X64 RyuJIT AVX2
  Job-DWUGHV : .NET Framework 4.8.1 (4.8.9105.0), X64 RyuJIT VectorSize=256

PowerPlanMode=00000000-0000-0000-0000-000000000000  
Method Job Toolchain Blocks InputType Mean Error StdDev Median Min Max Gen0 Gen1 Allocated
Formatting Job-HHIMXG .NET 7.0 1 Preformatted 14.64 ms 0.227 ms 0.177 ms 14.61 ms 14.48 ms 15.15 ms 62.5000 15.6250 4.64 MB
Formatting Job-DWUGHV .NET Framework 4.7.2 1 Preformatted 22.01 ms 0.145 ms 0.128 ms 22.02 ms 21.79 ms 22.22 ms 781.2500 62.5000 4.86 MB
Formatting Job-HHIMXG .NET 7.0 1 Unformatted 14.78 ms 0.212 ms 0.198 ms 14.79 ms 14.52 ms 15.19 ms 62.5000 - 4.61 MB
Formatting Job-DWUGHV .NET Framework 4.7.2 1 Unformatted 21.86 ms 0.087 ms 0.077 ms 21.85 ms 21.76 ms 22.01 ms 781.2500 62.5000 4.84 MB
Formatting Job-HHIMXG .NET 7.0 2 Preformatted 21.74 ms 0.197 ms 0.185 ms 21.74 ms 21.41 ms 21.97 ms 93.7500 31.2500 7.47 MB
Formatting Job-DWUGHV .NET Framework 4.7.2 2 Preformatted 33.42 ms 0.312 ms 0.292 ms 33.36 ms 33.01 ms 34.02 ms 1250.0000 125.0000 7.84 MB
Formatting Job-HHIMXG .NET 7.0 2 Unformatted 21.53 ms 0.128 ms 0.114 ms 21.50 ms 21.34 ms 21.80 ms 93.7500 31.2500 7.43 MB
Formatting Job-DWUGHV .NET Framework 4.7.2 2 Unformatted 33.54 ms 0.221 ms 0.196 ms 33.58 ms 33.21 ms 33.96 ms 1250.0000 125.0000 7.8 MB
Formatting Job-HHIMXG .NET 7.0 3 Preformatted 29.03 ms 0.193 ms 0.189 ms 29.01 ms 28.68 ms 29.45 ms 125.0000 31.2500 10.47 MB
Formatting Job-DWUGHV .NET Framework 4.7.2 3 Preformatted 49.37 ms 0.923 ms 0.863 ms 49.48 ms 47.27 ms 50.89 ms 1800.0000 200.0000 10.97 MB
Formatting Job-HHIMXG .NET 7.0 3 Unformatted 31.34 ms 0.641 ms 1.777 ms 31.14 ms 28.62 ms 36.18 ms 83.3333 - 10.48 MB
Formatting Job-DWUGHV .NET Framework 4.7.2 3 Unformatted 46.01 ms 0.406 ms 0.380 ms 46.02 ms 45.55 ms 46.88 ms 1818.1818 272.7273 10.96 MB
Formatting Job-HHIMXG .NET 7.0 4 Preformatted 36.53 ms 0.187 ms 0.175 ms 36.52 ms 36.14 ms 36.85 ms 142.8571 - 13.55 MB
Formatting Job-DWUGHV .NET Framework 4.7.2 4 Preformatted 58.91 ms 0.631 ms 0.590 ms 58.89 ms 58.19 ms 60.13 ms 2333.3333 333.3333 14.15 MB
Formatting Job-HHIMXG .NET 7.0 4 Unformatted 37.05 ms 0.255 ms 0.239 ms 37.05 ms 36.53 ms 37.43 ms 142.8571 71.4286 13.47 MB
Formatting Job-DWUGHV .NET Framework 4.7.2 4 Unformatted 58.75 ms 0.480 ms 0.449 ms 58.78 ms 58.19 ms 59.61 ms 2333.3333 333.3333 14.07 MB
Formatting Job-HHIMXG .NET 7.0 5 Preformatted 44.71 ms 0.470 ms 0.440 ms 44.65 ms 43.98 ms 45.39 ms 181.8182 90.9091 16.73 MB
Formatting Job-DWUGHV .NET Framework 4.7.2 5 Preformatted 72.62 ms 0.628 ms 0.556 ms 72.57 ms 71.82 ms 73.60 ms 2857.1429 571.4286 17.49 MB
Formatting Job-HHIMXG .NET 7.0 5 Unformatted 45.36 ms 0.301 ms 0.282 ms 45.31 ms 44.97 ms 45.92 ms 181.8182 - 16.69 MB
Formatting Job-DWUGHV .NET Framework 4.7.2 5 Unformatted 73.50 ms 1.282 ms 1.425 ms 72.67 ms 72.30 ms 76.75 ms 2857.1429 428.5714 17.42 MB
Formatting Job-HHIMXG .NET 7.0 10 Preformatted 93.76 ms 1.021 ms 0.905 ms 93.62 ms 92.48 ms 95.55 ms 500.0000 - 34.12 MB
Formatting Job-DWUGHV .NET Framework 4.7.2 10 Preformatted 152.82 ms 0.832 ms 0.695 ms 152.90 ms 151.71 ms 154.07 ms 5666.6667 666.6667 35.28 MB
Formatting Job-HHIMXG .NET 7.0 10 Unformatted 96.19 ms 1.751 ms 1.552 ms 95.92 ms 93.34 ms 99.13 ms 333.3333 - 33.81 MB
Formatting Job-DWUGHV .NET Framework 4.7.2 10 Unformatted 155.29 ms 1.865 ms 1.558 ms 155.05 ms 153.56 ms 159.65 ms 5750.0000 750.0000 35.02 MB
Formatting Job-HHIMXG .NET 7.0 20 Preformatted 218.58 ms 3.401 ms 3.181 ms 218.53 ms 211.91 ms 223.09 ms 1000.0000 - 75.99 MB
Formatting Job-DWUGHV .NET Framework 4.7.2 20 Preformatted 376.10 ms 1.719 ms 1.524 ms 375.90 ms 373.93 ms 379.00 ms 12000.0000 2000.0000 78.44 MB
Formatting Job-HHIMXG .NET 7.0 20 Unformatted 229.04 ms 3.617 ms 3.384 ms 229.41 ms 222.56 ms 234.88 ms 1000.0000 - 75.65 MB
Formatting Job-DWUGHV .NET Framework 4.7.2 20 Unformatted 389.45 ms 4.953 ms 4.391 ms 388.44 ms 383.95 ms 400.44 ms 12000.0000 2000.0000 78.1 MB
Formatting Job-HHIMXG .NET 7.0 30 Preformatted 376.12 ms 5.730 ms 5.359 ms 374.10 ms 367.98 ms 386.98 ms 1000.0000 - 126 MB
Formatting Job-DWUGHV .NET Framework 4.7.2 30 Preformatted 699.35 ms 13.688 ms 14.646 ms 700.83 ms 676.02 ms 722.31 ms 21000.0000 2000.0000 130.17 MB
Formatting Job-HHIMXG .NET 7.0 30 Unformatted 395.16 ms 6.099 ms 5.407 ms 395.43 ms 385.30 ms 407.43 ms 1000.0000 - 125.44 MB
Formatting Job-DWUGHV .NET Framework 4.7.2 30 Unformatted 700.95 ms 3.554 ms 2.968 ms 702.13 ms 696.25 ms 706.20 ms 21000.0000 2000.0000 129.63 MB
Formatting Job-HHIMXG .NET 7.0 40 Preformatted 576.94 ms 8.339 ms 7.800 ms 576.64 ms 562.32 ms 588.61 ms 2000.0000 1000.0000 186.32 MB
Formatting Job-DWUGHV .NET Framework 4.7.2 40 Preformatted 1,071.64 ms 8.781 ms 7.784 ms 1,071.97 ms 1,057.25 ms 1,087.04 ms 31000.0000 2000.0000 191.55 MB
Formatting Job-HHIMXG .NET 7.0 40 Unformatted 613.53 ms 4.965 ms 4.402 ms 613.95 ms 608.05 ms 623.89 ms 2000.0000 1000.0000 185.46 MB
Formatting Job-DWUGHV .NET Framework 4.7.2 40 Unformatted 1,093.18 ms 5.753 ms 4.804 ms 1,093.26 ms 1,082.37 ms 1,101.11 ms 31000.0000 2000.0000 190.86 MB
Formatting Job-HHIMXG .NET 7.0 100 Preformatted 2,575.15 ms 27.446 ms 24.330 ms 2,567.69 ms 2,534.08 ms 2,612.67 ms 9000.0000 3000.0000 728.46 MB
Formatting Job-DWUGHV .NET Framework 4.7.2 100 Preformatted 5,125.08 ms 78.876 ms 65.865 ms 5,112.53 ms 5,024.51 ms 5,249.09 ms 121000.0000 6000.0000 749.96 MB
Formatting Job-HHIMXG .NET 7.0 100 Unformatted 2,847.36 ms 42.931 ms 40.158 ms 2,849.60 ms 2,781.75 ms 2,940.95 ms 9000.0000 3000.0000 731.83 MB
Formatting Job-DWUGHV .NET Framework 4.7.2 100 Unformatted 5,429.82 ms 48.680 ms 45.536 ms 5,417.71 ms 5,364.95 ms 5,528.23 ms 121000.0000 8000.0000 748.1 MB

RazorCSharpFormattingBenchmark (with changes)

BenchmarkDotNet=v0.13.2.2052-nightly, OS=Windows 11 (10.0.25290.1010)
Intel Core i7-8700 CPU 3.20GHz (Coffee Lake), 1 CPU, 12 logical and 6 physical cores
.NET SDK=7.0.102
  [Host]     : .NET 7.0.3 (7.0.323.6910), X64 RyuJIT AVX2
  Job-HDSNVP : .NET 7.0.3 (7.0.323.6910), X64 RyuJIT AVX2
  Job-WOMZCU : .NET Framework 4.8.1 (4.8.9105.0), X64 RyuJIT VectorSize=256

PowerPlanMode=00000000-0000-0000-0000-000000000000  
Method Job Toolchain Blocks InputType Mean Error StdDev Median Min Max Gen0 Gen1 Allocated
Formatting Job-HDSNVP .NET 7.0 1 Preformatted 15.02 ms 0.059 ms 0.052 ms 15.01 ms 14.88 ms 15.09 ms 62.5000 15.6250 4.51 MB
Formatting Job-WOMZCU .NET Framework 4.7.2 1 Preformatted 21.69 ms 0.225 ms 0.211 ms 21.66 ms 21.46 ms 22.08 ms 781.2500 62.5000 4.73 MB
Formatting Job-HDSNVP .NET 7.0 1 Unformatted 14.40 ms 0.094 ms 0.088 ms 14.40 ms 14.22 ms 14.54 ms 62.5000 15.6250 4.49 MB
Formatting Job-WOMZCU .NET Framework 4.7.2 1 Unformatted 21.49 ms 0.331 ms 0.310 ms 21.45 ms 21.10 ms 21.95 ms 781.2500 62.5000 4.71 MB
Formatting Job-HDSNVP .NET 7.0 2 Preformatted 21.16 ms 0.394 ms 0.469 ms 21.00 ms 20.46 ms 22.16 ms 93.7500 31.2500 7.16 MB
Formatting Job-WOMZCU .NET Framework 4.7.2 2 Preformatted 32.09 ms 0.249 ms 0.220 ms 32.18 ms 31.59 ms 32.31 ms 1187.5000 125.0000 7.49 MB
Formatting Job-HDSNVP .NET 7.0 2 Unformatted 21.08 ms 0.163 ms 0.152 ms 21.13 ms 20.77 ms 21.35 ms 93.7500 31.2500 7.14 MB
Formatting Job-WOMZCU .NET Framework 4.7.2 2 Unformatted 31.89 ms 0.204 ms 0.170 ms 31.89 ms 31.63 ms 32.31 ms 1187.5000 125.0000 7.46 MB
Formatting Job-HDSNVP .NET 7.0 3 Preformatted 27.30 ms 0.203 ms 0.189 ms 27.29 ms 27.00 ms 27.56 ms 125.0000 31.2500 9.9 MB
Formatting Job-WOMZCU .NET Framework 4.7.2 3 Preformatted 42.79 ms 0.348 ms 0.291 ms 42.67 ms 42.45 ms 43.53 ms 1666.6667 250.0000 10.36 MB
Formatting Job-HDSNVP .NET 7.0 3 Unformatted 27.24 ms 0.080 ms 0.071 ms 27.25 ms 27.12 ms 27.33 ms 125.0000 31.2500 9.85 MB
Formatting Job-WOMZCU .NET Framework 4.7.2 3 Unformatted 42.86 ms 0.434 ms 0.406 ms 42.82 ms 42.29 ms 43.58 ms 1666.6667 250.0000 10.32 MB
Formatting Job-HDSNVP .NET 7.0 4 Preformatted 35.97 ms 0.570 ms 0.533 ms 36.14 ms 34.68 ms 36.67 ms - - 12.72 MB
Formatting Job-WOMZCU .NET Framework 4.7.2 4 Preformatted 53.60 ms 0.579 ms 0.484 ms 53.47 ms 52.91 ms 54.72 ms 2200.0000 500.0000 13.25 MB
Formatting Job-HDSNVP .NET 7.0 4 Unformatted 35.74 ms 0.575 ms 0.537 ms 35.68 ms 34.98 ms 36.84 ms - - 12.63 MB
Formatting Job-WOMZCU .NET Framework 4.7.2 4 Unformatted 53.66 ms 0.334 ms 0.296 ms 53.58 ms 53.29 ms 54.41 ms 2100.0000 300.0000 13.17 MB
Formatting Job-HDSNVP .NET 7.0 5 Preformatted 41.76 ms 0.364 ms 0.341 ms 41.80 ms 41.20 ms 42.39 ms 166.6667 - 15.48 MB
Formatting Job-WOMZCU .NET Framework 4.7.2 5 Preformatted 64.71 ms 0.672 ms 0.561 ms 64.49 ms 63.69 ms 65.87 ms 2625.0000 500.0000 16.19 MB
Formatting Job-HDSNVP .NET 7.0 5 Unformatted 42.29 ms 0.738 ms 0.725 ms 42.13 ms 41.35 ms 43.74 ms 166.6667 - 15.44 MB
Formatting Job-WOMZCU .NET Framework 4.7.2 5 Unformatted 67.44 ms 1.333 ms 1.869 ms 67.22 ms 65.10 ms 71.84 ms 2625.0000 375.0000 16.11 MB
Formatting Job-HDSNVP .NET 7.0 10 Preformatted 82.49 ms 1.270 ms 1.126 ms 82.39 ms 80.57 ms 84.77 ms 333.3333 - 29.99 MB
Formatting Job-WOMZCU .NET Framework 4.7.2 10 Preformatted 123.61 ms 1.334 ms 1.248 ms 124.01 ms 121.56 ms 125.47 ms 5000.0000 800.0000 31.02 MB
Formatting Job-HDSNVP .NET 7.0 10 Unformatted 86.10 ms 1.576 ms 1.397 ms 86.07 ms 83.89 ms 89.08 ms 333.3333 - 29.84 MB
Formatting Job-WOMZCU .NET Framework 4.7.2 10 Unformatted 127.28 ms 0.953 ms 0.845 ms 127.20 ms 126.02 ms 128.68 ms 5000.0000 750.0000 30.89 MB
Formatting Job-HDSNVP .NET 7.0 20 Preformatted 179.13 ms 1.939 ms 1.719 ms 178.74 ms 176.85 ms 182.96 ms - - 61.9 MB
Formatting Job-WOMZCU .NET Framework 4.7.2 20 Preformatted 262.90 ms 1.435 ms 1.272 ms 262.98 ms 260.45 ms 265.30 ms 10500.0000 1500.0000 63.23 MB
Formatting Job-HDSNVP .NET 7.0 20 Unformatted 188.57 ms 1.779 ms 1.664 ms 188.48 ms 185.98 ms 191.37 ms - - 61.53 MB
Formatting Job-WOMZCU .NET Framework 4.7.2 20 Unformatted 271.12 ms 2.035 ms 1.804 ms 270.62 ms 268.76 ms 275.00 ms 10000.0000 1000.0000 62.93 MB
Formatting Job-HDSNVP .NET 7.0 30 Preformatted 298.84 ms 5.598 ms 4.675 ms 297.82 ms 293.45 ms 311.24 ms 1000.0000 - 95.67 MB
Formatting Job-WOMZCU .NET Framework 4.7.2 30 Preformatted 432.20 ms 3.270 ms 3.058 ms 431.98 ms 427.07 ms 437.35 ms 16000.0000 2000.0000 98.93 MB
Formatting Job-HDSNVP .NET 7.0 30 Unformatted 330.12 ms 6.596 ms 9.028 ms 329.95 ms 316.54 ms 352.99 ms 1000.0000 - 95.17 MB
Formatting Job-WOMZCU .NET Framework 4.7.2 30 Unformatted 473.60 ms 9.257 ms 15.210 ms 468.25 ms 457.39 ms 510.25 ms 16000.0000 2000.0000 98.42 MB
Formatting Job-HDSNVP .NET 7.0 40 Preformatted 419.62 ms 3.948 ms 3.693 ms 419.15 ms 413.99 ms 425.08 ms 1000.0000 - 132.8 MB
Formatting Job-WOMZCU .NET Framework 4.7.2 40 Preformatted 651.68 ms 12.205 ms 11.417 ms 652.19 ms 633.28 ms 670.39 ms 22000.0000 2000.0000 137.22 MB
Formatting Job-HDSNVP .NET 7.0 40 Unformatted 465.43 ms 5.666 ms 5.023 ms 465.74 ms 458.52 ms 475.00 ms 1000.0000 - 132.03 MB
Formatting Job-WOMZCU .NET Framework 4.7.2 40 Unformatted 689.75 ms 9.230 ms 8.634 ms 687.15 ms 680.59 ms 707.96 ms 22000.0000 2000.0000 136.44 MB
Formatting Job-HDSNVP .NET 7.0 100 Preformatted 1,682.58 ms 24.273 ms 18.950 ms 1,686.12 ms 1,648.03 ms 1,704.47 ms 5000.0000 2000.0000 411.94 MB
Formatting Job-WOMZCU .NET Framework 4.7.2 100 Preformatted 2,313.18 ms 11.073 ms 10.357 ms 2,314.09 ms 2,290.22 ms 2,328.71 ms 70000.0000 7000.0000 423.73 MB
Formatting Job-HDSNVP .NET 7.0 100 Unformatted 1,960.09 ms 11.630 ms 9.080 ms 1,962.64 ms 1,937.36 ms 1,971.84 ms 4000.0000 2000.0000 410.13 MB
Formatting Job-WOMZCU .NET Framework 4.7.2 100 Unformatted 2,687.51 ms 15.160 ms 13.439 ms 2,691.80 ms 2,666.29 ms 2,708.59 ms 69000.0000 7000.0000 421.86 MB

@DustinCampbell DustinCampbell requested review from a team as code owners February 8, 2023 21:16
Copy link
Contributor

@davidwengier davidwengier left a comment

Choose a reason for hiding this comment

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

I had no idea the Children property was not part of the generated syntax node code, and was committing such crimes. Shocked pikachu.

Tooling side looks fine to me. Thanks for the awesome improvements!

Copy link
Member

@333fred 333fred left a comment

Choose a reason for hiding this comment

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

Done review pass (commit 4). I only looked at the compiler changes, not the IDE side.

@DustinCampbell DustinCampbell force-pushed the formatting-perf branch 4 times, most recently from 77b1d49 to 4e3fc99 Compare February 9, 2023 13:32
@DustinCampbell
Copy link
Member Author

@333fred @dotnet/razor-compiler - This is ready for another review.

@DustinCampbell
Copy link
Member Author

Thanks!

@DustinCampbell DustinCampbell merged commit 7bccbac into dotnet:main Feb 10, 2023
@DustinCampbell DustinCampbell deleted the formatting-perf branch February 10, 2023 02:13
Sign up for free to 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.

3 participants