New issue
Advanced search Search tips
Starred by 1 user
Status: Fixed
Owner:
Closed: Oct 18
Cc:
Components:
HW: All
OS: All
Priority: 1
Type: FeatureRequest
ES5

Blocking:
issue 6936



Sign in to add a comment
Generalize inlining of Function.prototype.bind into TurboFan
Project Member Reported by bmeu...@chromium.org, Oct 17 Back to list
Currently TurboFan can inline a call to Function#bind like

  g.bind(rcv, p1, p2, p3)

when g is a known constant, i.e. it's a property of the global object or some const declared in an outer context. But if g is just a regular parameter or loaded from somewhere we don't inline. We could however, because the LOAD_IC for g.bind already provides all the necessary information. This is illustrated by the following micro-benchmark:

=================< bench-function-bind.js >=========================
if (typeof console === 'undefined') console = {log:print};

const N = 5e7;
const TESTS = [];

(function() {
  const o = {};
  const g = x => x;

  TESTS.push(function functionBindParameter0(f) {
    return f.bind();
  },
  function functionBindConstant0(f) {
    return g.bind();
  },
  function functionBindParameter1(f) {
    return f.bind(o);
  },
  function functionBindConstant1(f) {
    return g.bind(o);
  },
  function functionBindParameter2(f) {
    return f.bind(o, 1);
  },
  function functionBindConstant2(f) {
    return g.bind(o, 1);
  });
})();

function bar() { return this; }

function test(fn) {
  var result;
  for (var i = 0; i < N; i += 1) result = fn(bar);
  return result;
}

for (var j = 0; j < TESTS.length; ++j) {
  test(TESTS[j]);
}

for (var j = 0; j < TESTS.length; ++j) {
  var startTime = Date.now();
  test(TESTS[j]);
  console.log(TESTS[j].name + ':', (Date.now() - startTime), 'ms.');
}
====================================================================

Running on V8 5.8, 6.0 and ToT we see:

====================================================================
$ ./d8-5.8.283.38 bench-function-bind.js
functionBindParameter0: 1205 ms.
functionBindConstant0: 1281 ms.
functionBindParameter1: 1231 ms.
functionBindConstant1: 1286 ms.
functionBindParameter2: 1360 ms.
functionBindConstant2: 1429 ms.

$ ./d8-6.0.286.36 bench-function-bind.js
functionBindParameter0: 1154 ms.
functionBindConstant0: 1141 ms.
functionBindParameter1: 1161 ms.
functionBindConstant1: 1196 ms.
functionBindParameter2: 1302 ms.
functionBindConstant2: 1275 ms.

$ ./d8-master bench-function-bind.js
functionBindParameter0: 1258 ms.
functionBindConstant0: 478 ms.
functionBindParameter1: 1261 ms.
functionBindConstant1: 472 ms.
functionBindParameter2: 1436 ms.
functionBindConstant2: 625 ms.
====================================================================

On the jshint test in the web-tooling-benchmark we seem to spend 2-3% of the overall time in the generic Function#bind implementation, which contains a lot of checks that TurboFan wouldn't need to perform.
 
Blocking: 6936
Slightly modified the benchmark to also measure impact on bound functions.

=================< bench-function-bind.js >=========================
if (typeof console === 'undefined') console = {log:print};

const N = 5e7;
const TESTS = [];

(function() {
  const o = {};
  const g = x => x;
  const h = g.bind(undefined);

  TESTS.push(function functionBindParameter0(f) {
    return f.bind();
  },
  function functionBindConstant0(f) {
    return g.bind();
  },
  function functionBindBoundConstant0(f) {
    return h.bind();
  },
  function functionBindParameter1(f) {
    return f.bind(o);
  },
  function functionBindConstant1(f) {
    return g.bind(o);
  },
  function functionBindBoundConstant1(f) {
    return h.bind(o);
  },
  function functionBindParameter2(f) {
    return f.bind(o, 1);
  },
  function functionBindConstant2(f) {
    return g.bind(o, 1);
  },
  function functionBindBoundConstant2(f) {
    return h.bind(o, 1);
  });
})();

function bar() { return this; }

function test(fn) {
  var result;
  for (var i = 0; i < N; i += 1) result = fn(bar);
  return result;
}

for (var j = 0; j < TESTS.length; ++j) {
  test(TESTS[j]);
}

for (var j = 0; j < TESTS.length; ++j) {
  var startTime = Date.now();
  test(TESTS[j]);
  console.log(TESTS[j].name + ':', (Date.now() - startTime), 'ms.');
}
====================================================================

For the record, V8 5.8 looks like this:

====================================================================
$ ./d8-5.8.283.38 bench-function-bind.js
functionBindParameter0: 1281 ms.
functionBindConstant0: 1318 ms.
functionBindBoundConstant0: 97357 ms.
functionBindParameter1: 1284 ms.
functionBindConstant1: 1330 ms.
functionBindBoundConstant1: 98963 ms.
functionBindParameter2: 1442 ms.
functionBindConstant2: 1518 ms.
functionBindBoundConstant2: 103598 ms.
====================================================================

Project Member Comment 4 by bugdroid1@chromium.org, Oct 17
The following revision refers to this bug:
  https://chromium.googlesource.com/v8/v8.git/+/594803c94671fa19db1420f2b43023b75264e101

commit 594803c94671fa19db1420f2b43023b75264e101
Author: Benedikt Meurer <bmeurer@chromium.org>
Date: Tue Oct 17 12:56:32 2017

[turbofan] Inline Function#bind in more cases.

So far the inlining of Function#bind into TurboFan optimized code was
limited to cases where TurboFan could infer the constant JSFunction that
was bound. However we can easily extend that to cover JSBoundFunction as
well, and obviously also take the LOAD_IC feedback if we don't have a
known JSFunction or JSBoundFunction.

This adds a new operator JSCreateBoundFunction that contains the logic
for the creation of the bound function object and the arguments.

On the micro-benchmarks we go from

  functionBindParameter0: 1239 ms.
  functionBindConstant0: 478 ms.
  functionBindBoundConstant0: 1256 ms.
  functionBindParameter1: 1278 ms.
  functionBindConstant1: 475 ms.
  functionBindBoundConstant1: 1253 ms.
  functionBindParameter2: 1431 ms.
  functionBindConstant2: 616 ms.
  functionBindBoundConstant2: 1437 ms.

to

  functionBindParameter0: 462 ms.
  functionBindConstant0: 485 ms.
  functionBindBoundConstant0: 474 ms.
  functionBindParameter1: 478 ms.
  functionBindConstant1: 474 ms.
  functionBindBoundConstant1: 474 ms.
  functionBindParameter2: 617 ms.
  functionBindConstant2: 614 ms.
  functionBindBoundConstant2: 616 ms.

which is a ~2.5x improvement. On the jshint benchmark in the
web-tooling-benchmark we observe a 2-3% improvement, which corresponds
to the time we had seen it running in the generic version.

Bug: v8:6936,  v8:6946 
Change-Id: I940d13220ff35ae602dbaa33349ba4bbe0c9a9d3
Reviewed-on: https://chromium-review.googlesource.com/723080
Reviewed-by: Jaroslav Sevcik <jarin@chromium.org>
Commit-Queue: Benedikt Meurer <bmeurer@chromium.org>
Cr-Commit-Position: refs/heads/master@{#48639}
[modify] https://crrev.com/594803c94671fa19db1420f2b43023b75264e101/src/compiler/js-builtin-reducer.cc
[modify] https://crrev.com/594803c94671fa19db1420f2b43023b75264e101/src/compiler/js-builtin-reducer.h
[modify] https://crrev.com/594803c94671fa19db1420f2b43023b75264e101/src/compiler/js-call-reducer.cc
[modify] https://crrev.com/594803c94671fa19db1420f2b43023b75264e101/src/compiler/js-call-reducer.h
[modify] https://crrev.com/594803c94671fa19db1420f2b43023b75264e101/src/compiler/js-create-lowering.cc
[modify] https://crrev.com/594803c94671fa19db1420f2b43023b75264e101/src/compiler/js-create-lowering.h
[modify] https://crrev.com/594803c94671fa19db1420f2b43023b75264e101/src/compiler/js-generic-lowering.cc
[modify] https://crrev.com/594803c94671fa19db1420f2b43023b75264e101/src/compiler/js-inlining.cc
[modify] https://crrev.com/594803c94671fa19db1420f2b43023b75264e101/src/compiler/js-operator.cc
[modify] https://crrev.com/594803c94671fa19db1420f2b43023b75264e101/src/compiler/js-operator.h
[modify] https://crrev.com/594803c94671fa19db1420f2b43023b75264e101/src/compiler/opcodes.h
[modify] https://crrev.com/594803c94671fa19db1420f2b43023b75264e101/src/compiler/typer.cc
[modify] https://crrev.com/594803c94671fa19db1420f2b43023b75264e101/src/compiler/verifier.cc

Status: Fixed
Sign in to add a comment