Optimize parser and tokenizer hot paths#30
Conversation
Apply AggressiveOptimization/AggressiveInlining hints to high-frequency methods and eliminate redundant branching in ParseClassElement using goto. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR applies JIT optimization hints (AggressiveOptimization and AggressiveInlining) to tokenizer and parser hot paths, and eliminates redundant branching in ParseClassElement using goto labels. It also adds angular-1.7.9 to the benchmark suite to measure improvements on ES6+ code paths. These are pure throughput improvements targeting both .NET and .NET Framework runtimes.
Changes:
- Added
AggressiveOptimizationto tokenizer hot paths (TryReadToken,SkipSpace,ReadWord1,ReadWord) and parser hot paths (ParseStatement,ParseSubscripts,ParseMaybeConditional,ParseMaybeBinary). - Added
AggressiveInliningtoCanInsertSemicolonandIsContextualutility methods. - Refactored
ParseClassElementto usegoto ParseElementNameto skip redundant null-checks and contextual-keywordEatcalls after determining a keyword is not acting as a modifier.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
src/Acornima/Tokenizer.cs |
Adds AggressiveOptimization to 4 core tokenization hot-path methods |
src/Acornima/Parser.Statement.cs |
Adds AggressiveOptimization to ParseStatement; refactors ParseClassElement with goto to eliminate redundant branching |
src/Acornima/Parser.ParseUtil.cs |
Adds AggressiveInlining to IsContextual and CanInsertSemicolon utility methods |
src/Acornima/Parser.Expression.cs |
Adds AggressiveOptimization to 4 expression-parsing hot-path methods |
benchmarks/Acornima.Benchmarks/FileParsingBenchmark.cs |
Adds angular-1.7.9 benchmark to cover ES6+ code paths |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
I knew there was something more there :) Getting 2-6% perf improvement by sprinkling some attributes here and there -> mind blown 🤯 Plus, this little change bumping the max. recursion depth from 610 back to 900 is just the icing on the cake! Big thanks! |
d1b48ec to
95d401e
Compare
95d401e to
cc1afbb
Compare
|
@lahma FYI, I had to revert most of the changes from this PR, as it turned out that not only didn't they improve the performance but caused 5-6% degradation, at least on my machine. I have no idea what's behind the difference. Maybe it's the hardware? Anyway, I spent a few hours doing benchmarks, and this was the best configuration I could come up with: d02b931 BTW, I also released the recent batch: https://github.com/adams85/acornima/releases/tag/v1.3.0 |
Thanks for the feedback, I need to rerun some tests evidently... I'm running on Ryzen 9 5950X so that might also introduce some differences between our setups.
Got that updated and 128 more tests passing again, yay 🚀 sebastienros/jint#2322 |
Summary
Performance optimization targeting tokenizer and parser hot paths, addressing issue #10. Applies
AggressiveOptimizationandAggressiveInliningJIT hints to high-frequency methods and eliminates redundant branching inParseClassElement.Changes
1.
AggressiveOptimizationon Tokenizer Hot Paths (Tokenizer.cs)These methods execute on every single token (~34K-68K calls for the largest benchmark files):
TryReadToken— Core token dispatch switch (~20 cases). AggressiveOptimization helps the JIT produce better jump tables for the large switch.SkipSpace— Called before every token. Tight character loop withGetCharFlagslookup table. The JIT can better optimize the inner loop and remove redundant bounds checks.ReadWord1— Identifier character reading loop, called for every identifier/keyword. Tight loop overFullCharCodeAtPosition()+IsIdentifierChar().ReadWord— Called afterReadWord1for keyword lookup + string deduplication. On every identifier path.2.
AggressiveOptimizationon Parser Hot Paths (Parser.Expression.cs,Parser.Statement.cs)ParseStatement— Called for every statement. Contains a large keyword switch dispatch — the most frequently called parser method.ParseSubscripts— Loop method over the already-optimizedParseSubscript. On the expression hot path for every member access, call, and optional chain.ParseMaybeConditional— Direct hot path betweenParseMaybeAssign(already optimized) andParseMaybeBinary. Small but very frequently called.ParseMaybeBinary— Entry point to binary operator parsing. Bridges toParseBinaryOp(already optimized) andParseMaybeUnary(already optimized).3.
AggressiveInliningon Small Utility Methods (Parser.ParseUtil.cs)CanInsertSemicolon— Called 12+ times across the parser. Body is just 3 field comparisons (~12 bytes native). Eliminates call overhead and enables the JIT to fold conditions into callers.IsContextual— Called 14+ times. Body is 3 field reads + comparisons. Already small enough for JIT auto-inlining on .NET Core, but the attribute guarantees it on .NET Framework too.4.
ParseClassElementgotoOptimization (Parser.Statement.cs)Eliminated redundant branching in
ParseClassElementby usinggoto ParseElementNameto skip checks that are known to be unnecessary once a keyword (static,async,get,set,accessor) is determined not to be a modifier.Before: When e.g.
staticis consumed but isn't a keyword (line 1378),keyName = "static"is set, then the code still evaluateskeyName is nullchecks beforeisAsync,isGenerator,get/set/accessorchecks, all of which are guaranteed false.After: Each fallthrough path jumps directly to element name parsing, skipping ~4-5 redundant null checks and
EatContextualcalls that can never succeed.Variables
isAsync,isGenerator,isAccessor,kindare moved to the top with default values (false/Unknown) so jumping over their previous declaration sites is safe.5. Benchmark: Add
angular-1.7.9(FileParsingBenchmark.cs)Added
angular-1.7.9.js(1.3MB, 71 class definitions) to the benchmark suite. The existing 7 files are all pre-ES6 and contain noclasssyntax, making theParseClassElementgoto optimization unmeasurable. This modern file provides coverage for ES6+ code paths.Benchmark Results
Environment: AMD Ryzen 9 5950X, Windows 11, BenchmarkDotNet v0.15.6
.NET 10.0
.NET Framework 4.8
Key observations:
angular-1.2.5baseline on net10.0 had a bimodal distribution warning and is excluded from the net10.0 table above.Test plan
🤖 Generated with Claude Code