Vector (original) (raw)

Feedback in V8

Feedback in V8

Michael Stanton

Feedback

The transmission of evaluative or corrective information about an action, event, or process to the original or controlling source; also : the information so transmitted

What people think we do

What people think we do

What we actually do

How does v8 achieve Performance?

Compilation pipeline with learning

Source

Code

Ignition

Byte

Code

Run for a while

Gather feedback

IC Slot Type Value
1 LOAD MONO
2 CALL UNINIT
... ... ...

Compilation pipeline with learning

Source

Code

Ignition

Turbofan

Byte

Code

Optimized

Code

Slot Type Value
1 LOAD MONO
2 CALL UNINIT
... ... ...

Compilation pipeline with learning

Source

Code

Ignition

Turbofan

Byte

Code

Optimized

Code

Deoptimization

Slot Type Value
1 LOAD MONO
2 CALL UNINIT
... ... ...

How does v8 achieve Performance?

Hidden-Class-based Object layout

let o = { x: 1 };
o.y = 2;

Evolution of o.map

Hidden-Class-based Object layout

let o = { x: 1 };
o.y = 2;

Evolution of o.map

Hidden-Class-based Object layout

let o = { x: 1 };
o.y = 2;

Evolution of o.map

Umm...Okay...

How does v8 achieve Performance?

Inline Caches to maintain and observe layout

IC Slot IC Type Value
1 LOAD MONO
2 CALL UNINIT
... ... ...

Every function has a Feedback Vector. It's just an array that holds state for each IC.

function processLogFile(fileName) {
  this.collectEntries = true;
  this.lastLogFileName_ = fileName;
  var line;
  while (line = readline()) {
    this.processLogLine(line);
  }
  print();
  print("Load: " + this.LoadIC);
  print("Store: " + this.StoreIC);
  ...
}
IC Slot IC Type State
... ... ...
10 LOAD MONO(M)
11 LOAD UNINITIALIZED

Inline Caches to maintain and observe layout

An Inline Cache is also a state machine.

Inline Caches to maintain and observe layout

mrale.ph/blog

function load(a) { return a.key; }
// pseudo-code for the LOAD IC:
if (vector[slot].state == MONO) {
  if (a.map == vector[slot].map) {
    return valueAtOffset(a, vector[slot].offset);
  } else {
    // Call into the Runtime for instructions.
    // Update IC state.
  }
} else {
  ..
}
Slot Type State Map Offset
0 LOAD_IC MONO m 0x8

Some facts

How v8 achieves Performance

Feedback Workflow

Let's look at a simple property load.

function load(a) {
  return a.key;
}

var o = { key: "usb" };
var o1 = { key: "port" };

for (var i = 0; i < 100000; i++) {
  load(o);
  load(o1);
}

Parsing

Here is the AST from parsing:

function load(a) {
  return a.key;
}

var o = { key: "usb" };
var o1 = { key: "port" };

for (var i = 0; i < 100000; i++) {
  load(o);
  load(o1);
}
// Run with --print-ast
FUNC
. NAME "load"
. PARAMS
. . VAR "a"
. RETURN
. . PROPERTY Slot(0) at 29
. . . VAR PROXY parameter[0] "a"
. . . NAME key

Parsing

Here is the AST from parsing:

function load(a) {
  return a.key;
}

var o = { key: "usb" };
var o1 = { key: "port" };

for (var i = 0; i < 100000; i++) {
  load(o);
  load(o1);
}
// Run with --print-ast
FUNC
. NAME "load"
. PARAMS
. . VAR "a"
. RETURN
. . PROPERTY Slot(0) at 29
. . . VAR PROXY parameter[0] "a"
. . . NAME key

Here is the feedback vector specification:

Compiling bytecode

function load(a) {
  return a.key;
}

var o = { key: "usb" };
var o1 = { key: "port" };

for (var i = 0; i < 100000; i++) {
  load(o);
  load(o1);
}

Here is the feedback vector specification:

// Run with --print-bytecode
"load" -- Parameter count 2

   StackCheck
   Nop
   LdaNamedProperty a0, [0], [3]  // "key"
   Return

Interpreting

function load(a) {
  return a.key;
}

var o = { key: "usb" };
var o1 = { key: "port" };

for (var i = 0; i < 100000; i++) {
  load(o);
  load(o1);
}

Here is the feedback vector:

Slot Type Value
0 LOAD_IC UNINIT

Interpreting

function load(a) {
  return a.key;
}

var o = { key: "usb" };
var o1 = { key: "port" };

for (var i = 0; i < 100000; i++) {
  load(o);
  load(o1);
}

We execute the LoadIC for "a.key"...

Slot Type Value
0 LOAD_IC UNINIT

Interpreting

function load(a) {
  return a.key;
}

var o = { key: "usb" };
var o1 = { key: "port" };

for (var i = 0; i < 100000; i++) {
  load(o);
  load(o1);
}

Return the answer & remember the Map.

Slot Type Value
0 LOAD_IC MONO(o.map)

Interpreting

function load(a) {
  return a.key;
}

var o = { key: "usb" };
var o1 = { key: "port" };

for (var i = 0; i < 100000; i++) {
  load(o);
  load(o1);
}

Now with a different object...

Slot Type Value
0 LOAD_IC MONO(o.map)

Interpreting

function load(a) {
  return a.key;
}

var o = { key: "usb" };
var o1 = { key: "port" };

for (var i = 0; i < 100000; i++) {
  load(o);
  load(o1);
}
Slot Type Value
0 LOAD_IC MONO(o.map)

Interpreting

function load(a) {
  return a.key;
}

var o = { key: "usb" };
var o1 = { key: "port" };

for (var i = 0; i < 100000; i++) {
  load(o);
  load(o1);
}
Slot Type Value
0 LOAD_IC MONO(o.map)

Profiling

function load(a) {
  return a.key;
}

var o = { key: "usb" };
var o1 = { key: "port" };

for (var i = 0; i < 100000; i++) {
  load(o);
  load(o1);
}

The Runtime Profiler asks:

// --trace-opt output
[marking <JSFunction load> for 
    optimized recompilation, 
  reason: small function, 
  ICs with typeinfo: 1/1 (100%), 
  generic ICs: 0/1 (0%)]
Slot Type Value
0 LOAD_IC MONO(o.map)

(Run with --nouse-osr to ensure we optimize load(), and not the whole script)

// load(a) - Turbofanned

push ebp             // Build frame             
mov  ebp,esp         //
push esi             //
push edi             //

mov  eax,[ebp+0x8]   // eax = a
test al,0x1          // is a an object?
jz   DEOPT_0         // If not, deoptimize

mov  ecx,[eax-0x1]   // ecx = a.map
mov  edx,0x37e0b4ad  // edx = o.map
cmp  ecx, edx
jnz  DEOPT_1         // if not same, deopt.
mov  eax,[eax+0xb]   // return a.key!

mov  esp,ebp         // Tear down frame
pop  ebp             //
ret  0x8             // return for realz

...

DEOPT_0: call 0x52d06000  // Goodbye...
DEOPT_1: call 0x52d0600a  // Also goodbye
// load(a) - Turbofanned

push ebp             // Build frame             
mov  ebp,esp         //
push esi             //
push edi             //

mov  eax,[ebp+0x8]   // eax = a
test al,0x1          // is a an object?
jz   DEOPT_0         // If not, deoptimize

mov  ecx,[eax-0x1]   // ecx = a.map
mov  edx,0x37e0b4ad  // edx = o.map
cmp  ecx, edx
jnz  DEOPT_1         // if not same, deopt.
mov  eax,[eax+0xb]   // return a.key!

mov  esp,ebp         // Tear down frame
pop  ebp             //
ret  0x8             // return for realz

...

DEOPT_0: call 0x52d06000  // Goodbye...
DEOPT_1: call 0x52d0600a  // Also goodbye
// load(a) - Turbofanned

push ebp             // Build frame             
mov  ebp,esp         //
push esi             //
push edi             //

mov  eax,[ebp+0x8]   // eax = a
test al,0x1          // is a an object?
jz   DEOPT_0         // If not, deoptimize

mov  ecx,[eax-0x1]   // ecx = a.map
mov  edx,0x37e0b4ad  // edx = o.map
cmp  ecx, edx
jnz  DEOPT_1         // if not same, deopt.
mov  eax,[eax+0xb]   // return a.key!

mov  esp,ebp         // Tear down frame
pop  ebp             //
ret  0x8             // return for realz

...

DEOPT_0: call 0x52d06000  // Goodbye...
DEOPT_1: call 0x52d0600a  // Also goodbye
// load(a) - Turbofanned

push ebp             // Build frame             
mov  ebp,esp         //
push esi             //
push edi             //

mov  eax,[ebp+0x8]   // eax = a
test al,0x1          // is a an object?
jz   DEOPT_0         // If not, deoptimize

mov  ecx,[eax-0x1]   // ecx = a.map
mov  edx,0x37e0b4ad  // edx = o.map
cmp  ecx, edx
jnz  DEOPT_1         // if not same, deopt.
mov  eax,[eax+0xb]   // return a.key!

mov  esp,ebp         // Tear down frame
pop  ebp             //
ret  0x8             // return for realz

...

DEOPT_0: call 0x52d06000  // Goodbye...
DEOPT_1: call 0x52d0600a  // Also goodbye
// load(a) - Turbofanned

push ebp             // Build frame             
mov  ebp,esp         //
push esi             //
push edi             //

mov  eax,[ebp+0x8]   // eax = a
test al,0x1          // is a an object?
jz   DEOPT_0         // If not, deoptimize

mov  ecx,[eax-0x1]   // ecx = a.map
mov  edx,0x37e0b4ad  // edx = o.map
cmp  ecx, edx
jnz  DEOPT_1         // if not same, deopt.
mov  eax,[eax+0xb]   // return a.key!

mov  esp,ebp         // Tear down frame
pop  ebp             //
ret  0x8             // return for realz

...

DEOPT_0: call 0x52d06000  // Goodbye...
DEOPT_1: call 0x52d0600a  // Also goodbye

Monomorphism: The Golden Idol

Monomorphism: The Golden Idol

Things to consider when optimizing

The more references there are to "a" downwind of the feedback, the greater the benefit of knowing the object's class.

Downwind includes inlined functions.

Things to consider when optimizing

// TurboFan emits something like this:

if (a.map == 0x43501231) {         // Maps embedded in code.
  return loadAtOffset(a, 0xc);     // No Feedback Vector.
} else if (a.map == 0x99503211) {
  return loadAtOffset(a, 0x10);
} else {
  DEOPTIMIZE();                    // Oh noes!
}

Things to consider when optimizing

Things to consider when optimizing

Things to consider when optimizing

You lose touch with the Feedback Vector, and have nowhere to put the new map after deoptimization.

Eventually V8 stops reoptimizing.

Things to consider when optimizing

Optimized execution

Slot Type Value
0 LOAD_IC MONO(o.map)
// load(a) - Turbofanned
...
mov  ecx,[eax-0x1]  // ecx = a.map
mov  edx,0x37e0b4ad // edx = o.map
cmp  ecx, edx
jnz  DEOPT_1        // if not same, deopt
mov  eax,[eax+0xb]  // return a.key
...

Feedback Vector not being used

function load(a) {
  return a.key;
}

var o = { key: "usb" };
var o1 = { key: "port" };

for (var i = 0; i < 100000; i++) {
  load(o);
  load(o1);
}

load({ key: "usb", name: "francis" });

Deopt path is taken

function load(a) {
  return a.key;
}

var o = { key: "usb" };
var o1 = { key: "port" };

for (var i = 0; i < 100000; i++) {
  load(o);
  load(o1);
}

load({ key: "usb", name: "francis" });
Slot Type Value
0 LOAD_IC MONO(o.map)
// load(a) - Turbofanned
...
mov  ecx,[eax-0x1]  // ecx = a.map
mov  edx,0x37e0b4ad // edx = o.map
cmp  ecx, edx
jnz  DEOPT_1        // if not same, deopt
mov  eax,[eax+0xb]  // return a.key
...

Interpreting

function load(a) {
  return a.key;
}

var o = { key: "usb" };
var o1 = { key: "port" };

for (var i = 0; i < 100000; i++) {
  load(o);
  load(o1);
}

load({ key: "usb", name: "francis" });
Slot Type Value
0 LOAD_IC POLY(o.map, new_map)

Conclusion

We are watching...

Conclusion

We are watching...

with love!

IC Slot IC Type Value
1 LOAD MONO
2 CALL UNINIT
... ... ...

Questions?

Shameless plug