-
-
Notifications
You must be signed in to change notification settings - Fork 5.7k
Improve isIdentifierName
performance
#13211
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
Conversation
charCodeAt is faster for BMP characters and most identifier names are ASCII.
This pull request is automatically built and testable in CodeSandbox. To see build info of the built libraries, click here or the icon next to each commit SHA. Latest deployment of this branch, based on commit c2c9660:
|
Build successful! You can test your changes in the REPL here: https://babeljs.io/repl/build/45633/ |
let cp = name.charCodeAt(i); | ||
if ((cp & 0xfc00) === 0xd800 && i + 1 < name.length) { | ||
const trail = name.charCodeAt(++i); | ||
cp = 0x10000 + ((cp & 0x3ff) << 10) + (trail & 0x3ff); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is manually reimplementing codePointAt(i)
faster than using the native one? 🤔
Or is the speedup done just by not using for-of
+Array.from
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think both removing Array.from
and charCodeAt
play a role here. I didn't test for-of
since when we get rid of Array.from
we have to use for
.
According to this benchmark, both codePointAt
and this polyfill runs as fast on non-ASCII words. But when it comes to ASCII words, charCodeAt
one is significantly faster.
# Fork two processes so they can get optimized independently
# `isIdentifierName2` is the `codePointAt` version)
(node --predictable ./benchmark/isIdentifierName1.bench.mjs )&
(node --predictable ./benchmark/isIdentifierName2.bench.mjs )&
current#isIdentifierName on 2 short ASCII words: 11496362 ops/sec ±2.63% (0ms)
current#isIdentifierName2 on 2 short ASCII words: 6652795 ops/sec ±1.6% (0ms)
current#isIdentifierName2 on 1 long ASCII words: 3236276 ops/sec ±0.64% (0ms)
current#isIdentifierName on 1 long ASCII words: 4462584 ops/sec ±0.54% (0ms)
current#isIdentifierName on 3 non-ASCII words: 280274 ops/sec ±0.36% (0.004ms)
current#isIdentifierName2 on 3 non-ASCII words: 287687 ops/sec ±0.35% (0.003ms)
It surprised me too. I think I should dive into the machine code and see what's happening for ASCII words.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Although V8 uses charCodeAt
for codePointAt
(code), codePointAt
is not inlined when compiled via TurboFan as we can see there is a call
instruction to the StringCodePointAt
-- B5 start (in loop 4) --
0x38d6008c40de 9e 48897dd8 REX.W movq [rbp-0x28],rdi
0x38d6008c40e2 a2 33f6 xorl rsi,rsi
0x38d6008c40e4 a4 488bdf REX.W movq rbx,rdi
[ Inlined Trampoline to StringCodePointAt
0x38d6008c40e7 a7 e894237aff call 0x38d600066480 (StringCodePointAt) ;; runtime entry
]
0x38d6008c40ec ac 837dd000 cmpl [rbp-0x30],0x0
0x38d6008c40f0 b0 0f854d000000 jnz 0x38d6008c4143 <+0x103>
A single call
instruction does not necessarily slow down the whole routine, but to issue a call
we have to prepare the function prologues and epilogues which is the runtime overhead of calling functions.
When we are using charCodeAt
, it is inlined so the runtime overhead can be stripped. I don't know why TurboFan does not inline StringCodePointAt
, maybe because charCodeAt
is implemented in CSA as a primitive used in StringCodePointAt
: https://source.chromium.org/chromium/chromium/src/+/master:v8/src/codegen/code-stub-assembler.cc;drc=221e331b49dfefadbc6fa40b0c68e6f97606d0b3;l=6822
I guess the performance difference here is due to not inlining codePointAt
. But I can be wrong as I am not very familiar with this topic.
/cc @bmeurer
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh awesome investigation! (btw, according to its bio bmeurer doesn't work on v8 anymore)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🏃♂️
I'm pretty sure the major performance difference is due to not using |
@JLHwung did you open an v8 issue about |
For reference I open a V8 issue: https://bugs.chromium.org/p/v8/issues/detail?id=11743 |
Thanks! |
devDependencies
Results from my local machine
Initially meant to test per-package benchmark setup, this PR sees up to 3x improvement for ASCII words. The function is not used by
@babel/parser
internally, instead it is exported via@babel/types
and used in systemjs and JSX transforms.This PR also adds more test cases to
isIdentifier*
.