8000 Performance Optimization - Live Bindings · Issue #18494 · webpack/webpack · GitHub
[go: up one dir, main page]

Skip to content
Performance Optimization - Live Bindings #18494
@bworline

Description

@bworline

Feature request

What is the expected behavior?

If a tree falls in a forest and no one is around to hear it, does it make a sound?

The basic idea is to preserve live binding semantics only when necessary and/or it could be noticed.

Typical generated harmony export code looks like this:

__webpack_require__.d = function(exports, definition) {
	for (var key in definition ){
		if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
			Object.defineProperty(exports, key, {
				enumerable: true,
				get: definition[key]
			});
		}
	}
};

...,
123: ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
	/* harmony export */ __webpack_require__.d(__webpack_exports__, {
	/* harmony export */   K: () => (/* binding */ setValue),
	/* harmony export */   U: () => (/* binding */ value)
	/* harmony export */ });

	let value = 0;
	function setValue(v) {
		value = v;
	}
})

It would be more performant if it was this:

...
			Object.defineProperty(exports, key, {
				enumerable: true,
				value: definition[key] // CHANGED
			});
...
	/* harmony export */ __webpack_require__.d(__webpack_exports__, {
	/* harmony export */   K: setValue, // CHANGED
	/* harmony export */   U: value // CHANGED
	/* harmony export */ });
...

Now the imported code is just referencing a variable directly:


/* harmony import */ var _shared_1__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(123);

console.log(_shared_1__WEBPACK_IMPORTED_MODULE_0__/* .value */ .U);   // U is a number, not a getter

The problem with this is the export setup is making a copy o 738B f value. setValue doesn't update the exported copy so liveness is lost.

The indirection needs to be preserved for value, but it isn't required for setValue because there's conceptually no setSetValue. In fact, the vast majority of all exports don't require the indirection because they are not modified in the exporting module.

Once liveness requirements are determined for each export, we now emit something like this:

__webpack_require__.d2 = function(exports, definition, liveDefinition) {
	for (var key in definition || {}) {
		if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
			Object.defineProperty(exports, key, {
				enumerable: true,
				value: definition[key]
			});
		}
	}
	for (var key in liveDefinition || {}) {
		if (__webpack_require__.o(liveDefinition, key) && !__webpack_require__.o(exports, key)) {
			Object.defineProperty(exports, key, {
				enumerable: true,
				get: liveDefinition[key]
			});
		}
	}
};

...,
123: ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
	/* harmony export */ __webpack_require__.d2(__webpack_exports__, {
	/* harmony export */   K: setValue,
	/* harmony export */ }, {
	/* harmony export */   U: () => (/* binding */ value)
	/* harmony export */ });

	let value = 0;
	function setValue(v) {
		value = v;
	}
})

What is motivation or use case for adding/changing the behavior?

There is a performance improvement both in file size and runtime execution as an additional level of indirection (getter function call) is removed in most cases.

How should this be implemented in your opinion?

Hook assign on the javascriptparser and look for assignments to exported variables. Any that have an assignment would retain the getter level of indirection. Also look for circular references (e.g. module A imports function1 from module B, module B imports function2 from module A, function1 and function2 call each other). These also require a bailout to retain the getter. Everything else can be a value copy because the export is never changed and immediate evaluation is not a problem.

Generated export declaration code in modules will also need to be moved to the bottom of the module scope (from the top of the scope where it is now) to prevent references to variables that haven't been declared yet. Previously the getter indirection had prevented immediate access to exported variables.

Are you willing to work on this yourself?

No

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Status

    Priority - Medium

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

      0