Skip to content

Instantly share code, notes, and snippets.

@dgp1130
Last active December 7, 2023 06:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dgp1130/b86b0909401b6632792ee71e7e6038df to your computer and use it in GitHub Desktop.
Save dgp1130/b86b0909401b6632792ee71e7e6038df to your computer and use it in GitHub Desktop.
HydroActive Class vs Functional Syntax Optimizations

HydroActive Class vs Functional Syntax Optimizations

This attempts to analyze the optimziation behavior in V8 when using HydroActive class and functional components to try to quantify the performance difference. It uses a local build of d8 and follows advice in An Introduction to Speculative Optimization in V8

functional.js and class.js contain simplified implementations of defining custom element methods with the two designs. functional.output and class.output contain the output of running d8 on each file.

Takeaways

  1. Optimizations do carry over between class component instances. When one instance is optimized, all instances are optimized.
  2. Optimizations do not carry over between components in the functional design. If comp.add is optimized, comp2.add will not automatically gain that optimization.
  3. The feedback vector is shared between functional components. functional.output lists 0x34bd000dacc9 as the feedback vector for both component add functions.
    • comp.sub does not contain a feedback vector and serves as a negative test of feedback vector behavior.
  4. The second component in functional.js seems to take significantly more effort to optimize. It requires around 500 invocations before V8 will optimize it (the first component does fine with 50). No idea why this takes significantly more calls.
    • Does sharing the feedback vector provide any value if it takes this much effort to trigger an optimization pass anyways? The browser could just as easily build a new feedback vector in that time.
class AbstractComponent {}
class MyComponent extends AbstractComponent {
hydrate() { }
add(a, b) {
return a + b;
}
sub(a, b) {
return a - b;
}
}
{
const comp = new MyComponent();
comp.hydrate();
// Generate feedback for `comp.add` for optimization.
for (let i = 0; i < 50; ++i) comp.add(1, 2);
console.log('=== Optimizing Component 1 ===');
%OptimizeFunctionOnNextCall(comp.add);
comp.add(1, 2);
console.log('=== Component 1 ===');
%DebugPrint(comp.add); // Prints feedback vector.
}
{
const comp = new MyComponent();
comp.hydrate();
// No loop to invoke generate feedback for `comp.add`.
// No need to call this, already optimized.
// %OptimizeFunctionOnNextCall(comp.add);
console.log('=== Component 2 ===');
%DebugPrint(comp.add); // Prints feedback vector.
console.log('=== Deoptimizing Component 2 ===');
comp.add(1.1, 2.2); // Triggers deopt.
}
$ v8/out/x64.debug/d8 --allow-natives-syntax --no-lazy-feedback-allocation --trace-deopt class.js
=== Optimizing Component 1 ===
=== Component 1 ===
DebugPrint: 0x47d001c98b1: [Function]
- map: 0x047d000c443d <Map[28](HOLEY_ELEMENTS)> [FastProperties]
- prototype: 0x047d000c42f1 <JSFunction (sfi = 0x47d000893e9)>
- elements: 0x047d000006cd <FixedArray[0]> [HOLEY_ELEMENTS]
- function prototype: <no-prototype-slot>
- shared_info: 0x047d000d9ee9 <SharedFunctionInfo add>
- name: 0x047d000040a1 <String[3]: #add>
- formal_parameter_count: 2
- kind: ConciseMethod
- context: 0x047d001c9865 <BlockContext[2]>
- code: 0x047d000da96d <Code TURBOFAN>
- source code: (a, b) {
return a + b;
}
- properties: 0x047d000006cd <FixedArray[0]>
- All own properties (excluding elements): {
0x47d00000d41: [String] in ReadOnlySpace: #length: 0x047d0030e3bd <AccessorInfo name= 0x047d00000d41 <String[6]: #length>, data= 0x047d00000061 <undefined>> (const accessor descriptor), location: descriptor
0x47d00000d6d: [String] in ReadOnlySpace: #name: 0x047d0030e3a5 <AccessorInfo name= 0x047d00000d6d <String[4]: #name>, data= 0x047d00000061 <undefined>> (const accessor descriptor), location: descriptor
}
- feedback vector: 0x47d000da70d: [FeedbackVector] in OldSpace
- map: 0x047d00000789 <Map(FEEDBACK_VECTOR_TYPE)>
- length: 1
- shared function info: 0x047d000d9ee9 <SharedFunctionInfo add>
- no optimized code
- tiering state: TieringState::kNone
- maybe has maglev code: 0
- maybe has turbofan code: 0
- invocation count: 50
- closure feedback cell array: 0x47d00001fd5: [ClosureFeedbackCellArray] in ReadOnlySpace
- map: 0x047d00000761 <Map(CLOSURE_FEEDBACK_CELL_ARRAY_TYPE)>
- length: 0
- elements:
- slot #0 BinaryOp BinaryOp:SignedSmall {
[0]: 1
}
0x47d000c443d: [Map] in OldSpace
- map: 0x047d000c3c29 <MetaMap (0x047d000c3c79 <NativeContext[285]>)>
- type: JS_FUNCTION_TYPE
- instance size: 28
- inobject properties: 0
- unused property fields: 0
- elements kind: HOLEY_ELEMENTS
- enum length: invalid
- callable
- back pointer: 0x047d00000061 <undefined>
- prototype_validity cell: 0x047d00000a31 <Cell value= 1>
- instance descriptors (own) #2: 0x047d000c4465 <DescriptorArray[2]>
- prototype: 0x047d000c42f1 <JSFunction (sfi = 0x47d000893e9)>
- constructor: 0x047d000c4395 <JSFunction Function (sfi = 0x47d00334765)>
- dependent code: 0x047d000006dd <Other heap object (WEAK_ARRAY_LIST_TYPE)>
- construction counter: 0
=== Component 2 ===
DebugPrint: 0x47d001c98b1: [Function]
- map: 0x047d000c443d <Map[28](HOLEY_ELEMENTS)> [FastProperties]
- prototype: 0x047d000c42f1 <JSFunction (sfi = 0x47d000893e9)>
- elements: 0x047d000006cd <FixedArray[0]> [HOLEY_ELEMENTS]
- function prototype: <no-prototype-slot>
- shared_info: 0x047d000d9ee9 <SharedFunctionInfo add>
- name: 0x047d000040a1 <String[3]: #add>
- formal_parameter_count: 2
- kind: ConciseMethod
- context: 0x047d001c9865 <BlockContext[2]>
- code: 0x047d000da96d <Code TURBOFAN>
- source code: (a, b) {
return a + b;
}
- properties: 0x047d000006cd <FixedArray[0]>
- All own properties (excluding elements): {
0x47d00000d41: [String] in ReadOnlySpace: #length: 0x047d0030e3bd <AccessorInfo name= 0x047d00000d41 <String[6]: #length>, data= 0x047d00000061 <undefined>> (const accessor descriptor), location: descriptor
0x47d00000d6d: [String] in ReadOnlySpace: #name: 0x047d0030e3a5 <AccessorInfo name= 0x047d00000d6d <String[4]: #name>, data= 0x047d00000061 <undefined>> (const accessor descriptor), location: descriptor
}
- feedback vector: 0x47d000da70d: [FeedbackVector] in OldSpace
- map: 0x047d00000789 <Map(FEEDBACK_VECTOR_TYPE)>
- length: 1
- shared function info: 0x047d000d9ee9 <SharedFunctionInfo add>
- no optimized code
- tiering state: TieringState::kNone
- maybe has maglev code: 0
- maybe has turbofan code: 0
- invocation count: 50
- closure feedback cell array: 0x47d00001fd5: [ClosureFeedbackCellArray] in ReadOnlySpace
- map: 0x047d00000761 <Map(CLOSURE_FEEDBACK_CELL_ARRAY_TYPE)>
- length: 0
- elements:
- slot #0 BinaryOp BinaryOp:SignedSmall {
[0]: 1
}
0x47d000c443d: [Map] in OldSpace
- map: 0x047d000c3c29 <MetaMap (0x047d000c3c79 <NativeContext[285]>)>
- type: JS_FUNCTION_TYPE
- instance size: 28
- inobject properties: 0
- unused property fields: 0
- elements kind: HOLEY_ELEMENTS
- enum length: invalid
- callable
- back pointer: 0x047d00000061 <undefined>
- prototype_validity cell: 0x047d00000a31 <Cell value= 1>
- instance descriptors (own) #2: 0x047d000c4465 <DescriptorArray[2]>
- prototype: 0x047d000c42f1 <JSFunction (sfi = 0x47d000893e9)>
- constructor: 0x047d000c4395 <JSFunction Function (sfi = 0x47d00334765)>
- dependent code: 0x047d000006dd <Other heap object (WEAK_ARRAY_LIST_TYPE)>
- construction counter: 0
=== Deoptimizing Component 2 ===
[bailout (kind: deopt-eager, reason: not a Smi): begin. deoptimizing 0x047d001c98b1 <JSFunction add (sfi = 0x47d000d9ee9)>, 0x047d000da96d <Code TURBOFAN>, opt id 0, node id 31, bytecode offset 2, deopt exit 0, FP to SP delta 32, caller SP 0x7ffcdbf549b0, pc 0x7f5c608c4151]
class AbstractComponent {}
function defineComponent(hydrate) {
return class extends AbstractComponent {
hydrate() {
const props = hydrate();
Object.defineProperties(this, Object.getOwnPropertyDescriptors(props));
}
};
}
const MyComponent = defineComponent(() => {
return {
add(a, b) {
return a + b;
},
sub(a, b) {
return a - b;
},
};
});
{
const comp = new MyComponent();
comp.hydrate();
// Generate feedback for `comp.add` for optimization.
for (let i = 0; i < 50; ++i) comp.add(1, 2);
console.log('=== Optimizing Component 1 ===');
%OptimizeFunctionOnNextCall(comp.add);
comp.add(1, 2);
console.log('=== Component 1 ===');
%DebugPrint(comp.add); // Prints feedback vector.
}
{
const comp = new MyComponent();
comp.hydrate();
// No loop to invoke generate feedback for `comp.add`.
// Feedback vector is retained for `add`.
console.log('=== Component 2 `add` ===');
%DebugPrint(comp.add); // Prints feedback vector.
console.log('=== Component 2 `sub` ===');
%DebugPrint(comp.sub); // Prints no feedback vector.
// This confirms V8 is able to correlate the multiple instances of `add`.
// Need to call this, not optimized.
console.log('=== Optimizing Component 2 ===');
%OptimizeFunctionOnNextCall(comp.add);
comp.add(1, 2);
// Or we can manually run `comp.add` to trigger optimization, however this
// requires ~500 iterations before optimizations will consistently kick in.
// for (let i = 0; i < 500; ++i) comp.add(1, 2);
console.log('=== Deoptimizing Component 2 ===');
comp.add(1.1, 2.2); // Triggers deopt.
}
$ v8/out/x64.debug/d8 --allow-natives-syntax --no-lazy-feedback-allocation --trace-deopt functional.js
=== Optimizing Component 1 ===
=== Component 1 ===
DebugPrint: 0x34bd001c9da9: [Function]
- map: 0x34bd000c443d <Map[28](HOLEY_ELEMENTS)> [FastProperties]
- prototype: 0x34bd000c42f1 <JSFunction (sfi = 0x34bd000893e9)>
- elements: 0x34bd000006cd <FixedArray[0]> [HOLEY_ELEMENTS]
- function prototype: <no-prototype-slot>
- shared_info: 0x34bd000da9fd <SharedFunctionInfo add>
- name: 0x34bd000040a1 <String[3]: #add>
- formal_parameter_count: 2
- kind: ConciseMethod
- context: 0x34bd000da34d <ScriptContext[4]>
- code: 0x34bd000daef1 <Code TURBOFAN>
- source code: (a, b) {
return a + b;
}
- properties: 0x34bd000006cd <FixedArray[0]>
- All own properties (excluding elements): {
0x34bd00000d41: [String] in ReadOnlySpace: #length: 0x34bd0030e3bd <AccessorInfo name= 0x34bd00000d41 <String[6]: #length>, data= 0x34bd00000061 <undefined>> (const accessor descriptor), location: descriptor
0x34bd00000d6d: [String] in ReadOnlySpace: #name: 0x34bd0030e3a5 <AccessorInfo name= 0x34bd00000d6d <String[4]: #name>, data= 0x34bd00000061 <undefined>> (const accessor descriptor), location: descriptor
}
- feedback vector: 0x34bd000dacc9: [FeedbackVector] in OldSpace
- map: 0x34bd00000789 <Map(FEEDBACK_VECTOR_TYPE)>
- length: 1
- shared function info: 0x34bd000da9fd <SharedFunctionInfo add>
- no optimized code
- tiering state: TieringState::kNone
- maybe has maglev code: 0
- maybe has turbofan code: 0
- invocation count: 50
- closure feedback cell array: 0x34bd00001fd5: [ClosureFeedbackCellArray] in ReadOnlySpace
- map: 0x34bd00000761 <Map(CLOSURE_FEEDBACK_CELL_ARRAY_TYPE)>
- length: 0
- elements:
- slot #0 BinaryOp BinaryOp:SignedSmall {
[0]: 1
}
0x34bd000c443d: [Map] in OldSpace
- map: 0x34bd000c3c29 <MetaMap (0x34bd000c3c79 <NativeContext[285]>)>
- type: JS_FUNCTION_TYPE
- instance size: 28
- inobject properties: 0
- unused property fields: 0
- elements kind: HOLEY_ELEMENTS
- enum length: invalid
- callable
- back pointer: 0x34bd00000061 <undefined>
- prototype_validity cell: 0x34bd00000a31 <Cell value= 1>
- instance descriptors (own) #2: 0x34bd000c4465 <DescriptorArray[2]>
- prototype: 0x34bd000c42f1 <JSFunction (sfi = 0x34bd000893e9)>
- constructor: 0x34bd000c4395 <JSFunction Function (sfi = 0x34bd00334765)>
- dependent code: 0x34bd000006dd <Other heap object (WEAK_ARRAY_LIST_TYPE)>
- construction counter: 0
=== Component 2 `add` ===
DebugPrint: 0x34bd001ca07d: [Function]
- map: 0x34bd000c443d <Map[28](HOLEY_ELEMENTS)> [FastProperties]
- prototype: 0x34bd000c42f1 <JSFunction (sfi = 0x34bd000893e9)>
- elements: 0x34bd000006cd <FixedArray[0]> [HOLEY_ELEMENTS]
- function prototype: <no-prototype-slot>
- shared_info: 0x34bd000da9fd <SharedFunctionInfo add>
- name: 0x34bd000040a1 <String[3]: #add>
- builtin: CompileLazy
- formal_parameter_count: 2
- kind: ConciseMethod
- context: 0x34bd000da34d <ScriptContext[4]>
- code: 0x34bd00310d3d <Code BUILTIN CompileLazy>
- interpreted
- bytecode: 0x05d0000023fd <BytecodeArray[6]>
- source code: (a, b) {
return a + b;
}
- properties: 0x34bd000006cd <FixedArray[0]>
- All own properties (excluding elements): {
0x34bd00000d41: [String] in ReadOnlySpace: #length: 0x34bd0030e3bd <AccessorInfo name= 0x34bd00000d41 <String[6]: #length>, data= 0x34bd00000061 <undefined>> (const accessor descriptor), location: descriptor
0x34bd00000d6d: [String] in ReadOnlySpace: #name: 0x34bd0030e3a5 <AccessorInfo name= 0x34bd00000d6d <String[4]: #name>, data= 0x34bd00000061 <undefined>> (const accessor descriptor), location: descriptor
}
- feedback vector: 0x34bd000dacc9: [FeedbackVector] in OldSpace
- map: 0x34bd00000789 <Map(FEEDBACK_VECTOR_TYPE)>
- length: 1
- shared function info: 0x34bd000da9fd <SharedFunctionInfo add>
- no optimized code
- tiering state: TieringState::kNone
- maybe has maglev code: 0
- maybe has turbofan code: 0
- invocation count: 50
- closure feedback cell array: 0x34bd00001fd5: [ClosureFeedbackCellArray] in ReadOnlySpace
- map: 0x34bd00000761 <Map(CLOSURE_FEEDBACK_CELL_ARRAY_TYPE)>
- length: 0
- elements:
- slot #0 BinaryOp BinaryOp:SignedSmall {
[0]: 1
}
0x34bd000c443d: [Map] in OldSpace
- map: 0x34bd000c3c29 <MetaMap (0x34bd000c3c79 <NativeContext[285]>)>
- type: JS_FUNCTION_TYPE
- instance size: 28
- inobject properties: 0
- unused property fields: 0
- elements kind: HOLEY_ELEMENTS
- enum length: invalid
- callable
- back pointer: 0x34bd00000061 <undefined>
- prototype_validity cell: 0x34bd00000a31 <Cell value= 1>
- instance descriptors (own) #2: 0x34bd000c4465 <DescriptorArray[2]>
- prototype: 0x34bd000c42f1 <JSFunction (sfi = 0x34bd000893e9)>
- constructor: 0x34bd000c4395 <JSFunction Function (sfi = 0x34bd00334765)>
- dependent code: 0x34bd000006dd <Other heap object (WEAK_ARRAY_LIST_TYPE)>
- construction counter: 0
=== Component 2 `sub` ===
DebugPrint: 0x34bd001ca099: [Function]
- map: 0x34bd000c443d <Map[28](HOLEY_ELEMENTS)> [FastProperties]
- prototype: 0x34bd000c42f1 <JSFunction (sfi = 0x34bd000893e9)>
- elements: 0x34bd000006cd <FixedArray[0]> [HOLEY_ELEMENTS]
- function prototype: <no-prototype-slot>
- shared_info: 0x34bd000daa3d <SharedFunctionInfo sub>
- name: 0x34bd002a0eed <String[3]: #sub>
- builtin: CompileLazy
- formal_parameter_count: 2
- kind: ConciseMethod
- context: 0x34bd000da34d <ScriptContext[4]>
- code: 0x34bd00310d3d <Code BUILTIN CompileLazy>
- source code: (a, b) {
return a - b;
}
- properties: 0x34bd000006cd <FixedArray[0]>
- All own properties (excluding elements): {
0x34bd00000d41: [String] in ReadOnlySpace: #length: 0x34bd0030e3bd <AccessorInfo name= 0x34bd00000d41 <String[6]: #length>, data= 0x34bd00000061 <undefined>> (const accessor descriptor), location: descriptor
0x34bd00000d6d: [String] in ReadOnlySpace: #name: 0x34bd0030e3a5 <AccessorInfo name= 0x34bd00000d6d <String[4]: #name>, data= 0x34bd00000061 <undefined>> (const accessor descriptor), location: descriptor
}
- feedback vector: feedback metadata is not available in SFI
0x34bd000c443d: [Map] in OldSpace
- map: 0x34bd000c3c29 <MetaMap (0x34bd000c3c79 <NativeContext[285]>)>
- type: JS_FUNCTION_TYPE
- instance size: 28
- inobject properties: 0
- unused property fields: 0
- elements kind: HOLEY_ELEMENTS
- enum length: invalid
- callable
- back pointer: 0x34bd00000061 <undefined>
- prototype_validity cell: 0x34bd00000a31 <Cell value= 1>
- instance descriptors (own) #2: 0x34bd000c4465 <DescriptorArray[2]>
- prototype: 0x34bd000c42f1 <JSFunction (sfi = 0x34bd000893e9)>
- constructor: 0x34bd000c4395 <JSFunction Function (sfi = 0x34bd00334765)>
- dependent code: 0x34bd000006dd <Other heap object (WEAK_ARRAY_LIST_TYPE)>
- construction counter: 0
=== Optimizing Component 2 ===
=== Deoptimizing Component 2 ===
[bailout (kind: deopt-eager, reason: not a Smi): begin. deoptimizing 0x34bd001ca07d <JSFunction add (sfi = 0x34bd000da9fd)>, 0x34bd000db0cd <Code TURBOFAN>, opt id 1, node id 31, bytecode offset 2, deopt exit 0, FP to SP delta 40, caller SP 0x7fff90ace948, pc 0x7fb1846442d5]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment