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

【翻译】webpack中的对象构造器 #6

Open
879479119 opened this issue Nov 14, 2017 · 2 comments
Open

【翻译】webpack中的对象构造器 #6

879479119 opened this issue Nov 14, 2017 · 2 comments

Comments

@879479119
Copy link
Owner

webpack中的对象构造器

昨天在webpack这个Issue上面有人提到了个很有趣的问题,大概是说了3版本的webpack生成的代码是如何做到的引入了新的模块。比如像下面这样:

import foo from "module";

foo(1, 2);  // <- called without this (undefined/global object)

打包处理后会被转化成如下的样子:

var __WEBPACK_IMPORTED_MODULE_1__module__ = __webpack_require__(1);

Object(__WEBPACK_IMPORTED_MODULE_1__module__.foo)(1, 2);
// ^ called without this (undefined/global object)

这背后的原因是webpack必须把保留潜在的语义上的this给函数foo。如果你是像下面这样生成代码的话

var __WEBPACK_IMPORTED_MODULE_1__module__ = __webpack_require__(1);

__WEBPACK_IMPORTED_MODULE_1__module__.foo(1, 2);
// ^ called this = __WEBPACK_IMPORTED_MODULE_1__module__

在foo里面用到this的地方会被绑定到__WEBPACK_IMPORTED_MODULE_1__module__上,这样一来就和我们上面的ESM语义是不相符的了。很显然还有很多其他不同的方法来实现这一效果,Tobias Koppers研究了这其中的很多细节,尝试了多种方法,最终决定使用Object constructor来做

不幸的是 事实上对象构造函数在某些情况下仍然带来了一些不必要的损失,因为直到现在TurboFan(V8底层)还没有搞清楚怎么一回事,我简单的写了个benchmark

const identity = x => x;

function callDirect(o) {
  const foo = o.foo;
  return foo(1, 2, 3);
}

function callViaCall(o) {
  return o.foo.call(undefined, 1, 2, 3);
}

function callViaObject(o) {
  return Object(o.foo)(1, 2, 3);
}

function callViaIdentity(o) {
  return identity(o.foo)(1, 2, 3);
}

var TESTS = [
    callDirect,
    callViaObject,
    callViaCall,
    callViaIdentity
];

class A { foo(x, y, z) { return x + y + z; } };
var o = new A;
var n = 1e8;

function test(fn) {
  var result;
  for (var i = 0; i < n; ++i) result = fn(o);
  return result;
}

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

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

在V8.5.5(使用Crankshaft的最新版本)和V8.6.1(当前使用TurboFan的beta版本)中,从结果看来webpack的选择是正确的做法??

$ ./d8-5.8.283.38 bench-object-constructor.js
callDirect: 598 ms.
callViaObject: 1352 ms.
callViaCall: 645 ms.
callViaIdentity: 663 ms.
$ ./d8-6.1.534.15 bench-object-constructor.js
callDirect: 560 ms.
callViaObject: 1322 ms.
callViaCall: 613 ms.
callViaIdentity: 561 ms.

使用identity函数的版本达到了最好的效果(最接近直接调用的结果,目前webpack无法实现)。其次就是使用Function.proptype.call的方式了,然后使用Object constructor的方式就是最慢的了,是直接执行的2.3倍耗时

导致这一事件的原因是TurboFan和Crankshaft在执行callViaIdentity的时候将identity函数进行了内联(不知道C++ inline的自己去面壁),因此几乎完全没有造成什么额外的消耗

pic

但是调用Object constructor的时候没有被内联(我也看不懂汇编,不过明显看到多出来的步骤吧)

pic2

在这种情况下,在o.foo的环境中调用Object constructor相当于是一个noop函数(不知道的去面壁),就跟调用identify函数一样。我们可以在规范中找到相应的描述

document

ToObject抽象操作是js中对象的定义操作(closures就是普通的js对象)。所以我们就是得教会处理TurboFan这样的调用

Object(value)

当能够证明这个value是个object的时候。这就足以满足webpack的情况了,因为这里传递给Object constructor的是从module中拿到的对象,他们是始终被V8引擎所跟踪的,所以TurboFan能够追溯到module.hot的来源就是某个常量对象。所以能够添加一些Object constructor相关的黑魔法到TurboFan上面来解决这个issue

$ out/Release/d8 bench-object-constructor.js
callDirect: 562 ms.
callViaObject: 564 ms.
callViaCall: 615 ms.
callViaIdentity: 564 ms.

可以看到上面性能上有了的提升,或多或少的改变了些代码

pic3

并且webpack3生成的打包资源,也不回因为这些问题而有那么大差异了

pic4

嘛,其实全文读下来你会发现没说什么东西,而且现在的新版本上这几种操作的速度基本一致了。不过这也让我们知道了不断深挖的重要性

原文链接:V8核心维护者Benedikt Meurer的博客

@Cweili
Copy link

Cweili commented Jan 5, 2018

第一个代码块应该是

import { foo } from "module";
//_____^

foo(1, 2);  // <- called without this (undefined/global object)

或者第二个代码块应该是

var __WEBPACK_IMPORTED_MODULE_1__module__ = __webpack_require__(1);

Object(__WEBPACK_IMPORTED_MODULE_1__module__.default)(1, 2);
//___________________________________________^
// ^ called without this (undefined/global object)

@Cweili
Copy link

Cweili commented Jan 5, 2018

import { foo } from "module";

foo(1, 2);  // <- called without this (undefined/global object)

事实上,上面这段代码 webpack 转换后是这样

var __WEBPACK_IMPORTED_MODULE_1__module__ = __webpack_require__(1);

Object(__WEBPACK_IMPORTED_MODULE_1__module__.b /* foo */)(1, 2);
// ^ called without this (undefined/global object)

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

2 participants