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

lib: faster type checks for some types #15663

Closed
wants to merge 2 commits into from

Conversation

TimothyGu
Copy link
Member

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.

                                                 improvement confidence      p.value
 buffers/buffer-compare.js millions=30 size=0        23.84 %        *** 1.301551e-11
 buffers/buffer-compare.js millions=30 size=32       23.22 %        *** 5.364324e-13
 buffers/buffer-compare.js millions=10 size=4096     10.57 %        *** 2.771584e-13

scatter-plot

Checklist
  • make -j4 test (UNIX), or vcbuild test (Windows) passes
  • tests and/or benchmarks are included
  • commit message follows commit guidelines
Affected core subsystem(s)

lib

@nodejs-github-bot nodejs-github-bot added the lib / src Issues and PRs related to general changes in the lib or src directory. label Sep 28, 2017
const ReflectApply = Reflect.apply;

function uncurryThis(func) {
return (thisArg, ...args) => ReflectApply(func, thisArg, args);
Copy link
Member

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?

Copy link
Member Author

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.

Copy link
Member

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.

Copy link
Member Author

Choose a reason for hiding this comment

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

@BridgeAR comment added.

const TypedArrayProto_toStringTag =
uncurryThis(getterFunc(TypedArrayPrototype, Symbol.toStringTag));

const isArrayBufferView = ArrayBuffer.isView;
Copy link
Member

@BridgeAR BridgeAR Sep 28, 2017

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?

@mscdex mscdex added the performance Issues and PRs related to the performance of Node.js. label Sep 28, 2017
@cjihrig
Copy link
Contributor

cjihrig commented Sep 29, 2017

Having some of these type checking functions in util, and others in this new location isn't very intuitive IMO. I think we should consolidate in one of those locations.

@TimothyGu
Copy link
Member Author

@cjihrig The difference is that the type checkers in util are both public and deprecated.

@cjihrig
Copy link
Contributor

cjihrig commented Sep 29, 2017

Right now we have type checking functions in util, internal/util, and process.binding('util'). Regardless of their status, at least the common theme of util makes sense.

@TimothyGu
Copy link
Member Author

@cjihrig Would internal/util/types work for you? internal/util has too much stuff unrelated to type checking, and I'm not sure why the only type checker (isError) in that file is there because it's exposed through util anyway.

@cjihrig
Copy link
Contributor

cjihrig commented Sep 29, 2017

That works for me.

@bmeurer
Copy link
Member

bmeurer commented Sep 29, 2017

So I'm not entirely sure what is the scope of this change. Is it because ArrayBuffer.isView is not inlined into TurboFan? If so, that would be easy to fix in V8.

@TimothyGu
Copy link
Member Author

So I'm not entirely sure what is the scope of this change. Is it because ArrayBuffer.isView is not inlined into TurboFan? If so, that would be easy to fix in V8.

No; this PR has two parts:

  1. Use a faster version of some type checks (in the case of TypedArray and Uint8Array), and
  2. Prevent user code tampering with globals from affecting Node.js core by caching the untampered functions.

The change about ArrayBuffer.isView is of the latter ilk only. I'm 100% happy with the performance of it (though if you can improve it further then go ahead)!

@bmeurer
Copy link
Member

bmeurer commented Sep 29, 2017

Ah, right, just discovered that. The isArrayBufferView part was confusing me. I'll dig into ArrayBuffer.isView to speed that up a bit.

@lpinca
Copy link
Member

lpinca commented Sep 29, 2017

The isArrayBufferView part was confusing me.

Yes, I agree. No strong opinions, but maybe it's better to move that part to its own pr or commit.

@bmeurer
Copy link
Member

bmeurer commented Sep 29, 2017

Ok, I have a CL in flight that improves ArrayBuffer.isView by roughly 2.5x, and should allow for more optimizations in the surrounding code.

@TimothyGu
Copy link
Member Author

TimothyGu commented Sep 30, 2017

@bmeurer If possible, please optimize %TypedArray%.prototype[Symbol.toStringTag]'s getter as well. I'm using it quite heavily in this PR. Thanks 😊

@TimothyGu
Copy link
Member Author

@cjihrig Can you check if this version is acceptable for you? I've renamed it to internal/util/types

Copy link
Member

@bmeurer bmeurer left a 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) {
Copy link
Member

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.

Copy link
Member Author

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)).

Copy link
Member

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?

Copy link
Member Author

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.

Copy link
Member

Choose a reason for hiding this comment

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

I see. Makes sense.

const TypedArrayPrototype = Object.getPrototypeOf(Uint8Array.prototype);

const TypedArrayProto_toStringTag =
uncurryThis(getterFunc(TypedArrayPrototype, Symbol.toStringTag));
Copy link
Member

@bmeurer bmeurer Sep 30, 2017

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.

Copy link
Member Author

@TimothyGu TimothyGu Sep 30, 2017

Choose a reason for hiding this comment

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

Okay. Done.

kisg pushed a commit to paul99/v8mips that referenced this pull request Sep 30, 2017
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}
@TimothyGu
Copy link
Member Author

@bmeurer thanks for taking a look!

At this point, TypedArray.prototype[Symbol.toStringTag] is not really fast in V8. It's also not super-slow.

Yeah I realized. The %_IsTypedArray check is inlined which is pretty nice, but %_ClassOf calls into the runtime which isn't as nice, but still not as slow as it was before.

@BridgeAR
Copy link
Member

BridgeAR commented Oct 1, 2017

@BridgeAR
Copy link
Member

BridgeAR commented Oct 2, 2017

Landed in 7907534 and f547db1

@BridgeAR BridgeAR closed this Oct 2, 2017
kisg pushed a commit to paul99/v8mips that referenced this pull request Oct 2, 2017
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}
@bmeurer
Copy link
Member

bmeurer commented Oct 2, 2017

V8 optimization for TypedArray.prototype[Symbol.toStringTag] is v8:6874.

@TimothyGu TimothyGu deleted the is-uint8array branch October 2, 2017 08:24
@MylesBorins
Copy link
Contributor

This does not land cleanly on v8.x, could you please backport

addaleax pushed a commit to addaleax/ayo that referenced this pull request Oct 4, 2017
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]>
addaleax pushed a commit to addaleax/ayo that referenced this pull request Oct 4, 2017
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]>
TimothyGu added a commit to TimothyGu/node that referenced this pull request Oct 22, 2017
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]>
TimothyGu added a commit to TimothyGu/node that referenced this pull request Oct 22, 2017
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]>
evanlucas pushed a commit that referenced this pull request Oct 23, 2017
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]>
evanlucas pushed a commit that referenced this pull request Oct 23, 2017
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]>
@MylesBorins
Copy link
Contributor

Should this be backported to v6.x-staging? If yes please follow the guide and raise a backport PR, if not let me know or add the dont-land-on label.

kevinoid added a commit to kevinoid/node that referenced this pull request Apr 27, 2020
This function was added by nodejs#15663, but was never documented.
This commit documents it.
gireeshpunathil pushed a commit that referenced this pull request May 2, 2020
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]>
targos pushed a commit that referenced this pull request May 4, 2020
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]>
targos pushed a commit that referenced this pull request May 7, 2020
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]>
targos pushed a commit that referenced this pull request May 13, 2020
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]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
lib / src Issues and PRs related to general changes in the lib or src directory. performance Issues and PRs related to the performance of Node.js.
Projects
None yet
Development

Successfully merging this pull request may close these issues.