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

[feature-request][discussion][babel-plugin-transform-es2015-parameters] decrease byte size of transformed function (T6892) #3927

Open
babel-bot opened this issue Dec 27, 2015 · 14 comments

Comments

@babel-bot
Copy link
Collaborator

Issue originally made by @fabiomcosta

Description

The current rest transformation adds a bunch of code to the function in order to avoid the function body to get deoptimized by V8.

function foo(a, ...bar) {
}

// is transformed to:

function foo(a) {
  for (var _len1 = arguments.length, bar = Array(_len1 > 1 ? _len1 - 1 : 0), _key1 = 1; _key1 < _len1; _key1++) {
    bar[_key1 - s] = arguments[_key1];
  }
}

But since arguments used as the second parameter of the apply function doesn't deoptimizes a function body, we could change that to

function sliceArguments() {
  for (var s = this, _len1 = arguments.length, args = Array(_len1 > s ? _len1 - s : 0), _key1 = s; _key1 < _len1; _key1++) {
    args[_key1 - s] = arguments[_key1];
  }
  return args;
}
function foo(a) {
  var bar = sliceArguments.apply(1, arguments);
}

On multiple instances of functions using rest params this could greatly decrease the byte size.
Let me know what you guys think.

@hzoo
Copy link
Member

hzoo commented Mar 16, 2017

Looks like it could be a helper yeah!

@apapirovski
Copy link
Contributor

As far as I understand it, passing arguments to apply can certainly lead to deopt on older V8 (before 6.1 or 6.0, I think...). I wouldn't recommend making this change.

@Andarist
Copy link
Member

Andarist commented Nov 7, 2017

Maybe @bmeurer could shed some light on this one? Is it worth to implement such helper to reduce bundle sizes?

@apapirovski
Copy link
Contributor

Some research yields:

d3/d3-selection#111
nodejs/node#13384

Node has specifically avoided using .apply(x, arguments) because it can deoptimize. We've only introduced it back in after switching to 6.1.

@bmeurer
Copy link
Member

bmeurer commented Nov 8, 2017

It's definitely fine by now because it no longer disables optimization for that function (which BTW is not the same as deoptimization), but the helper approach will likely be slower.

@fabiomcosta
Copy link
Contributor

Some of these optimizations were targeted for the old V8 infrastructure, with Crankshaft.
With Turbofan everything has to be re-evaluated.

@bmeurer, on Turbofan, would the following implementation disable optimizations for the foo function?

function foo(a) {
  var bar = Array.prototype.slice.call(arguments, 1);
}

The main improvement/suggestion from this issue is to decrease the amount of code when we have multiple functions using variable arguments (fn(...args){}).

@apapirovski
Copy link
Contributor

apapirovski commented Nov 8, 2017

I don't think the issue there would be disabling optimizations but rather that manually copying is faster than slice, especially for a small number of arguments. (I know that doesn't apply to the latest V8 but Babel obviously would have to worry about quite a few versions back.)

With latest node, for example, it's about 2.4x faster to use the in place copying of arguments than Array.prototype.slice.call. (The test was done with a function that has 5 arguments.)

@bmeurer
Copy link
Member

bmeurer commented Nov 11, 2017

TurboFan doesn't disable optimization for anything, except when your function is way too big (really, really huge), or when it runs out of virtual registers/stack slots while trying to compile the function (unlikely). @apapirovski is right, it's about the performance penalty that you still incur with current versions in Node.

@fabiomcosta
Copy link
Contributor

Sounds like a helper function like this is what we are looking for then:

function sliceArgumentsBy(by, _args) {
  for (var s = by, _len1 = _args.length, args = Array(_len1 > s ? _len1 - s : 0), _key1 = s; _key1 < _len1; _key1++) {
    args[_key1 - s] = _args[_key1];
  }
  return args;
}
function foo(a) {
  var bar = sliceArgumentsBy(1, arguments);
}

@apapirovski
Copy link
Contributor

Since the whole point of transpilation is to find common ground where all browsers can execute the code, it doesn't seem like a good idea to use something that causes issues on older versions of V8 (and presumably it might do the same for other engines). It's not like we're talking just about babel's runtime here where the version of V8 is more controlled.

If a helper is desired then using it with apply should be safer but it might still be slower than inlining it like it's done currently.

@fabiomcosta
Copy link
Contributor

If a helper is desired then using it with apply should be safer but it might still be slower than inlining it like it's done currently.

@apapirovski correct, slower on runtime, but the transpiled code will end up having less bytes, which will make the final code load faster.
It's a trade-off, like most things in life haha

Thanks for all the input so far folks!

@bmeurer
Copy link
Member

bmeurer commented Nov 12, 2017

This helper is likely going to disable optimization on older versions of V8 (before TurboFan), since the arguments object escapes.

@fabiomcosta
Copy link
Contributor

@bmeurer then I guess we could use the one I suggested initially on the issue.

@bmeurer
Copy link
Member

bmeurer commented Nov 12, 2017

Function.prototype.apply is a safe use of arguments in that case, yes. But it's still going to be slower in older versions (or other engines). TurboFan should by now get it fully optimized. It's trade-off space.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants