-
Notifications
You must be signed in to change notification settings - Fork 30k
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
lib: faster type checks for some types #15663
Conversation
lib/internal/types.js
Outdated
const ReflectApply = Reflect.apply; | ||
|
||
function uncurryThis(func) { | ||
return (thisArg, ...args) => ReflectApply(func, thisArg, args); |
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.
The args will always be a empty array. So you might just use a empty array that you pass in for the argumentsList
instead of creating a new array each time?
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.
The spread syntax is actually faster than hardcoding an empty array by a good amount. Probably because of the fact that Reflect.apply is inlined.
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.
Interesting and good to know. In that case a comment about that would be good as well.
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.
@BridgeAR comment added.
lib/internal/types.js
Outdated
const TypedArrayProto_toStringTag = | ||
uncurryThis(getterFunc(TypedArrayPrototype, Symbol.toStringTag)); | ||
|
||
const isArrayBufferView = ArrayBuffer.isView; |
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.
Would you be so kind and add a comment that you added this to make sure it will not be manipulated by userland code?
Having some of these type checking functions in |
@cjihrig The difference is that the type checkers in |
Right now we have type checking functions in |
@cjihrig Would |
That works for me. |
So I'm not entirely sure what is the scope of this change. Is it because |
No; this PR has two parts:
The change about |
Ah, right, just discovered that. The |
Yes, I agree. No strong opinions, but maybe it's better to move that part to its own pr or commit. |
Ok, I have a CL in flight that improves |
@bmeurer If possible, please optimize |
bb151db
to
8d5d39d
Compare
@cjihrig Can you check if this version is acceptable for you? I've renamed it to |
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.
@TimothyGu At this point, TypedArray.prototype[Symbol.toStringTag]
is not really fast in V8. It's also not super-slow. I'll try to get that fixed and inlined next week.
As for this particular PR, I'm not sure uncurryThis
is really optimized fully with V8 6.1.
// with the spread syntax, such that no additional special case is needed for | ||
// function calls w/o arguments. | ||
// Refs: https://github.com/v8/v8/blob/d6ead37d265d7215cf9c5f768f279e21bd170212/src/js/prologue.js#L152-L156 | ||
function uncurryThis(func) { |
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.
Have you checked the generated code for isUint8Array
? I don't think we get the Reflect.apply
optimization with V8 6.1 already.
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.
Yes I have:
--- Raw source ---
(value) {
return TypedArrayProto_toStringTag(value) === 'Uint8Array';
}
--- Optimized code ---
optimization_id = 0
source_position = 1086
kind = OPTIMIZED_FUNCTION
name = isUint8Array
stack_slots = 4
compiler = turbofan
Instructions (size = 194)
0x367169e055c0 0 55 push rbp
0x367169e055c1 1 4889e5 REX.W movq rbp,rsp
0x367169e055c4 4 56 push rsi
0x367169e055c5 5 57 push rdi
0x367169e055c6 6 493ba5e80c0000 REX.W cmpq rsp,[r13+0xce8]
0x367169e055cd d 0f8656000000 jna 0x367169e05629 <+0x69>
0x367169e055d3 13 48bf59b6c858e83c0000 REX.W movq rdi,0x3ce858c8b659 ;; object: 0x3ce858c8b659 <JSFunction b (sfi = 0x2fc5e3493879)>
0x367169e055dd 1d 488b7727 REX.W movq rsi,[rdi+0x27]
0x367169e055e1 21 488b5d10 REX.W movq rbx,[rbp+0x10]
0x367169e055e5 25 53 push rbx
0x367169e055e6 26 498b55a0 REX.W movq rdx,[r13-0x60]
0x367169e055ea 2a 33c0 xorl rax,rax
0x367169e055ec 2c ff5737 call [rdi+0x37]
0x367169e055ef 2f a801 test al,0x1
0x367169e055f1 31 0f844e000000 jz 0x367169e05645 <+0x85>
0x367169e055f7 37 488b58ff REX.W movq rbx,[rax-0x1]
0x367169e055fb 3b f6430bc0 testb [rbx+0xb],0xc0
0x367169e055ff 3f 0f8545000000 jnz 0x367169e0564a <+0x8a>
0x367169e05605 45 48bb711549e3c52f0000 REX.W movq rbx,0x2fc5e3491571 ;; object: 0x2fc5e3491571 <String[10]: Uint8Array>
0x367169e0560f 4f 483bd8 REX.W cmpq rbx,rax
0x367169e05612 52 0f840b000000 jz 0x367169e05623 <+0x63>
0x367169e05618 58 498b45c0 REX.W movq rax,[r13-0x40]
0x367169e0561c 5c 488be5 REX.W movq rsp,rbp
0x367169e0561f 5f 5d pop rbp
0x367169e05620 60 c21000 ret 0x10
0x367169e05623 63 498b45b8 REX.W movq rax,[r13-0x48]
0x367169e05627 67 ebf3 jmp 0x367169e0561c <+0x5c>
0x367169e05629 69 48bb1067f50000000000 REX.W movq rbx,0xf56710
0x367169e05633 73 33c0 xorl rax,rax
0x367169e05635 75 488b75f8 REX.W movq rsi,[rbp-0x8]
0x367169e05639 79 e8a2efe7ff call 0x367169c845e0 ;; code: STUB, CEntryStub, minor: 8
0x367169e0563e 7e eb93 jmp 0x367169e055d3 <+0x13>
0x367169e05640 80 e8bbe9cfff call 0x367169b04000 ;; deoptimization bailout 0
0x367169e05645 85 e8c0e9cfff call 0x367169b0400a ;; deoptimization bailout 1
0x367169e0564a 8a e8c5e9cfff call 0x367169b04014 ;; deoptimization bailout 2
0x367169e0564f 8f e8cae9cfff call 0x367169b0401e ;; deoptimization bailout 3
0x367169e05654 94 90 nop
0x367169e05655 95 90 nop
...
Source positions:
pc offset position
26 553
69 1086
Inlined functions (count = 1)
0x3ce858ce6719 <SharedFunctionInfo args>
Deoptimization Input Data (deopt points = 4)
index bytecode-offset trampoline_pc pc
0 28 80 2f
1 15 85 NA
2 15 8a NA
3 0 8f 7e
Safepoints (size = 30)
0x367169e055ef 2f 0000 (sp -> fp) 0
0x367169e0563e 7e 0000 (sp -> fp) 3
RelocInfo (size = 11)
0x367169e055d5 embedded object (0x3ce858c8b659 <JSFunction b (sfi = 0x2fc5e3493879)>)
0x367169e05607 embedded object (0x2fc5e3491571 <String[10]: Uint8Array>)
0x367169e0563a code target (STUB) (0x367169c845e0)
0x367169e05641 runtime entry (deoptimization bailout 0)
0x367169e05646 runtime entry (deoptimization bailout 1)
0x367169e0564b runtime entry (deoptimization bailout 2)
0x367169e05650 runtime entry (deoptimization bailout 3)
--- End code ---
Note: function b()
is in fact %TypedArray%.prototype.toStringTag
, and args
is the name given to the wrapper function created in uncurryThis
, which as you can tell is fully inlined.
Note 2: I don't really care if Reflect.apply
is fully optimized for now, because regardless of that this function is much faster than the check we are currently using (#15663 (comment)).
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.
Ok, with more recent V8 this will get even better (I also have a patch to inline TypedArray.prototype[@@toStringTag]
). Nevertheless, why don't we just use F.p.call
here and avoid uncurryThis
completely?
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.
@bmeurer Function.prototype.call
can be overwritten by userland code. I don't want that possibility for a type checking function.
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 see. Makes sense.
lib/internal/util/types.js
Outdated
const TypedArrayPrototype = Object.getPrototypeOf(Uint8Array.prototype); | ||
|
||
const TypedArrayProto_toStringTag = | ||
uncurryThis(getterFunc(TypedArrayPrototype, Symbol.toStringTag)); |
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.
Nit: This took me some time to understand. Maybe just a comment would help, or inline the getterFunc
to make it obvious what is being done.
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.
Okay. Done.
This improves performance of ArrayBuffer.isView by roughly 2.5x itself, and enables optimizations across ArrayBuffer.isView calls, i.e. map checks can be eliminated across. This was discovered in a related Node pull request (nodejs/node#15663). Bug: v8:6868 Change-Id: I1d56ec385f8daa0e1d44d3bc4d6c9a5558ba4522 Reviewed-on: https://chromium-review.googlesource.com/691660 Reviewed-by: Yang Guo <[email protected]> Commit-Queue: Benedikt Meurer <[email protected]> Cr-Commit-Position: refs/heads/master@{#48247}
@bmeurer thanks for taking a look!
Yeah I realized. The |
3a63918
to
6116c39
Compare
The TypedArray.prototype[Symbol.toStringTag] getter is currently the best (and as far as I can tell only definitely side-effect free) way to check whether an arbitrary object is a TypedArray - either generally TypedArray or a specific one like Uint8Array. Using the getter is thus emerging as the general pattern to detect TypedArrays, even Node.js now adapted it starting with nodejs/node#15663 for the isTypedArray and isUint8Array type checks in lib/internal/util/types.js now. The getter returns either the string with the TypedArray subclass name (i.e. "Uint8Array") or undefined if the receiver is not a TypedArray. This can be implemented with a simple elements kind dispatch, instead of checking the instance type and then loading the class name from the constructor, which requires a loop walking up the transition tree. This CL ports the builtin to CSA and TurboFan, and changes the logic to a simple elements kind check. On the micro-benchmark mentioned in the referenced bug, the time goes from testIsArrayBufferView: 565 ms. testIsTypedArray: 2403 ms. testIsUint8Array: 3847 ms. to testIsArrayBufferView: 566 ms. testIsTypedArray: 965 ms. testIsUint8Array: 965 ms. which presents an up to 4x improvement. Bug: v8:6874 Change-Id: I9c330b4529d9631df2f052acf023c6a4fae69611 Reviewed-on: https://chromium-review.googlesource.com/695021 Reviewed-by: Jaroslav Sevcik <[email protected]> Commit-Queue: Benedikt Meurer <[email protected]> Cr-Commit-Position: refs/heads/master@{#48254}
V8 optimization for |
This does not land cleanly on v8.x, could you please backport |
PR-URL: nodejs/node#15663 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Ruben Bridgewater <[email protected]> Reviewed-By: Luigi Pinca <[email protected]> Reviewed-By: Benedikt Meurer <[email protected]> Reviewed-By: Colin Ihrig <[email protected]> Reviewed-By: James M Snell <[email protected]>
PR-URL: nodejs/node#15663 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Ruben Bridgewater <[email protected]> Reviewed-By: Luigi Pinca <[email protected]> Reviewed-By: Benedikt Meurer <[email protected]> Reviewed-By: Colin Ihrig <[email protected]> Reviewed-By: James M Snell <[email protected]>
Backport-PR-URL: nodejs#16073 PR-URL: nodejs#15663 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Ruben Bridgewater <[email protected]> Reviewed-By: Luigi Pinca <[email protected]> Reviewed-By: Benedikt Meurer <[email protected]> Reviewed-By: Colin Ihrig <[email protected]> Reviewed-By: James M Snell <[email protected]>
Backport-PR-URL: nodejs#16073 PR-URL: nodejs#15663 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Ruben Bridgewater <[email protected]> Reviewed-By: Luigi Pinca <[email protected]> Reviewed-By: Benedikt Meurer <[email protected]> Reviewed-By: Colin Ihrig <[email protected]> Reviewed-By: James M Snell <[email protected]>
Backport-PR-URL: #16073 PR-URL: #15663 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Ruben Bridgewater <[email protected]> Reviewed-By: Luigi Pinca <[email protected]> Reviewed-By: Benedikt Meurer <[email protected]> Reviewed-By: Colin Ihrig <[email protected]> Reviewed-By: James M Snell <[email protected]>
Backport-PR-URL: #16073 PR-URL: #15663 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Ruben Bridgewater <[email protected]> Reviewed-By: Luigi Pinca <[email protected]> Reviewed-By: Benedikt Meurer <[email protected]> Reviewed-By: Colin Ihrig <[email protected]> Reviewed-By: James M Snell <[email protected]>
Should this be backported to |
This function was added by nodejs#15663, but was never documented. This commit documents it.
This function was added by #15663, but was never documented. This commit documents it. PR-URL: #33092 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Luigi Pinca <[email protected]> Reviewed-By: Gireesh Punathil <[email protected]> Reviewed-By: Masashi Hirano <[email protected]> Reviewed-By: Juan José Arboleda <[email protected]> Reviewed-By: Andrey Pechkurov <[email protected]>
This function was added by #15663, but was never documented. This commit documents it. PR-URL: #33092 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Luigi Pinca <[email protected]> Reviewed-By: Gireesh Punathil <[email protected]> Reviewed-By: Masashi Hirano <[email protected]> Reviewed-By: Juan José Arboleda <[email protected]> Reviewed-By: Andrey Pechkurov <[email protected]>
This function was added by #15663, but was never documented. This commit documents it. PR-URL: #33092 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Luigi Pinca <[email protected]> Reviewed-By: Gireesh Punathil <[email protected]> Reviewed-By: Masashi Hirano <[email protected]> Reviewed-By: Juan José Arboleda <[email protected]> Reviewed-By: Andrey Pechkurov <[email protected]>
This function was added by #15663, but was never documented. This commit documents it. PR-URL: #33092 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Luigi Pinca <[email protected]> Reviewed-By: Gireesh Punathil <[email protected]> Reviewed-By: Masashi Hirano <[email protected]> Reviewed-By: Juan José Arboleda <[email protected]> Reviewed-By: Andrey Pechkurov <[email protected]>
The JS<->C++ overhead is fairly high with TurboFan. If there is a way to do a reliable and performant type check in JS, use JS instead.
This PR introduces a new internal module that also protects against modification of the global environment by caching all utilized functions at Node.js startup.
Checklist
make -j4 test
(UNIX), orvcbuild test
(Windows) passesAffected core subsystem(s)
lib