Skip to content

Reduce allocations on parser hot paths to improve performance#279

Merged
jfmengels merged 4 commits intostil4m:masterfrom
dillonkearns:perf/combined-optimizations
Apr 3, 2026
Merged

Reduce allocations on parser hot paths to improve performance#279
jfmengels merged 4 commits intostil4m:masterfrom
dillonkearns:perf/combined-optimizations

Conversation

@dillonkearns
Copy link
Copy Markdown
Contributor

This gives about 10-30% faster parsing beyond the results of #278 (15-44% faster between #278 and this PR combined), as measured by running the benchmarks against ~/.elm/0.19.1/.

Optimizations

  1. Hoist str ++ "" to parser construction time in symbol/keyword functions. Previously this string concatenation ran on every parse attempt. Now it's computed once when the parser is built.
  2. Use < 0 instead of == -1 since == compiles to _Utils_eq which allocates an empty [] on every call. < 0 compiles to _Utils_cmp which is allocation-free. These checks run thousands of times per file (once per identifier, number, and symbol first-char check).
  3. Inline charCodeIsLower, charCodeIsUpper, charCodeIsDigit range checks directly in unicodeIsAlphaNumOrUnderscoreFast. This eliminates 3 function calls per character on the hottest path in the parser.
  4. Pre-compute char codes in isNotRelevant (comment character scanning) to use Int comparison instead of Char equality via _Utils_eq.
  5. Add direct case expression checks for "0"-"9" to the subExpression first-character dispatch so number literals go directly to numberExpression instead of falling through to oneOf3 which tries two reference parsers first before reaching the number parser.

@lue-bird
Copy link
Copy Markdown
Contributor

lue-bird commented Apr 3, 2026

Thank you ❤️

Use < 0 instead of == -1 since == compiles to _Utils_eq

That's wrong. == with a literal compiles to ===: https://github.com/elm/compiler/blob/cce7a8bbd8fe690fc83fa795f8d7e02505d1f25f/compiler/src/Generate/JavaScript/Expression.hs#L550-L555

Hoist str ++ "" to parser construction time in symbol/keyword functions

Have you benchmarked if this specific step actually makes things faster compared to just removing the ++ ""? I have a feeling the ++ "" was redundant in the first place since current elm optimization steps do not compile to === even for known string appends.

@dillonkearns
Copy link
Copy Markdown
Contributor Author

That's wrong. == with a literal compiles to ===: https://github.com/elm/compiler/blob/cce7a8bbd8fe690fc83fa795f8d7e02505d1f25f/compiler/src/Generate/JavaScript/Expression.hs#L550-L555

This is true for positive int literals and string literals, but not for negative int literals. -1 is compiled as JS.Prefix PrefixNegate (JS.Int 1) rather than JS.Int (-1), so isLiteral returns False and equal falls back to _Utils_eq. So appears to be a bug in that optimization within the Elm compiler.

Here's a script that shows the compiled output: https://gist.github.com/dillonkearns/289891a7fa75b95b0336ffa25f0f77d6

$  curl https://gist.githubusercontent.com/dillonkearns/289891a7fa75b95b0336ffa25f0f77d6/raw/7dfdb3a092f3b94858a635d6ddb1da5ee27ceb7e/elm-eq-demo.sh | sh
Elm 0.19.1 --optimize output:

x == -1   =>  _Utils_eq(x, -1);    // not optimized (negation is a prefix expression)
x == 1    =>  x === 1;              // optimized (positive int literal)
s == "hi" =>  s === 'hello';        // optimized (string literal)
x < 0     =>  x < 0                 // direct comparison

Have you benchmarked if this specific step actually makes things faster? I have a feeling the ++ "" was redundant in the first place since current elm optimization steps do not compile to === even for known string appends.

Good point, I agree that this was redundant before so better to just remove it.

For reference, these were originally added in a series of commits that referred to "Use JS comparison where possible". I think it was likely attempting to get the Elm compiler output to use === but in fact ended up compiling to output more like _Utils_eq(slice, str + ''), so it didn't have the desired effect.

I went ahead and removed it in this particular area for now.

Thanks for the review!

@jfmengels jfmengels merged commit e20f6e4 into stil4m:master Apr 3, 2026
1 of 3 checks passed
@jfmengels
Copy link
Copy Markdown
Collaborator

Thank you, this is a big perf increase!

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