1 of 52

ESnext Performance

Why bother?

Benedikt Meurer

Proprietary

Proprietary

2 of 52

Benedikt Meurer

  • JavaScript Execution Optimization
  • Biking & Hiking
  • @bmeurer

Proprietary

3 of 52

ESnext = ES2015+

Designer

Developer

Implementor

Proprietary

4 of 52

Proprietary

5 of 52

“[...] large proportion of developers have already jumped on the [ES6] bandwagon, and almost all of those who haven’t yet want to learn it [...]“

”[...] in terms of satisfaction the big winner here is ES6, and [...] it’s now the default way to write JavaScript

Proprietary

Proprietary

6 of 52

Proprietary

7 of 52

Proprietary

8 of 52

ESnext today

...

Proprietary

9 of 52

ESnext tomorrow

+

Proprietary

10 of 52

ES2015 support is strong

Proprietary

11 of 52

ES2015 support is strong

Proprietary

12 of 52

ESnext - Two worlds

Native ES2015+ code

  • Usually hand-written
  • modular

Generated ES3/ES5 code

  • transpiled
  • bundled

Proprietary

13 of 52

function sum(a) {let s = 0;for (let x of a) s += x;return s;}

function sum(a) {var s = 0;var _iteratorNormalCompletion = true;var _didIteratorError = false;var _iteratorError = undefined;try {for (var _iterator = a[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {var x = _step.value;s += x;}} catch (err) {_didIteratorError = true;_iteratorError = err;} finally {try {if (!_iteratorNormalCompletion && _iterator.return) {_iterator.return();}} finally {if (_didIteratorError) {throw _iteratorError;}}}return s;}

Proprietary

14 of 52

function sum(a) {let s = 0;for (let x of a) s += x;return s;}

function sum(a) { var s = 0; var _iteratorNormalCompletion = true;var _didIteratorError = false;var _iteratorError = undefined;try { for (var _iterator = a[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {var x = _step.value;s += x;}} catch (err) {� _didIteratorError = true;� _iteratorError = err;} finally {try {if (!_iteratorNormalCompletion && _iterator.return) {� _iterator.return();}} finally {if (_didIteratorError) {throw _iteratorError;}}} return s;}

Proprietary

15 of 52

function sum(a) {let s = 0;for (let x of a) s += x;return s;}

function sum(a) {var s = 0;var _iteratorNormalCompletion = true;var _didIteratorError = false;var _iteratorError = undefined;try {for (var _iterator = a[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {var x = _step.value;� s += x;}} catch (err) {� _didIteratorError = true;� _iteratorError = err;} finally {try {if (!_iteratorNormalCompletion && _iterator.return) {� _iterator.return();}} finally {if (_didIteratorError) {throw _iteratorError;}}}return s;}

71 chars

651 chars

Proprietary

16 of 52

function sum(a) {let s = 0;for (let x of a) s += x;return s;}

function sum(a) {var s = 0;for (var _iterator = a, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) {var _ref;if (_isArray) {if (_i >= _iterator.length) break;_ref = _iterator[_i++];} else {_i = _iterator.next();if (_i.done) break;_ref = _i.value;}var x = _ref;s += x;}

return s;}

es2015-loose

Proprietary

17 of 52

function sum(a) {let s = 0;for (let x of a) s += x;return s;}

function sum(a) {var s = 0;for (var _iterator = a, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) {var _ref;if (_isArray) {if (_i >= _iterator.length) break;� _ref = _iterator[_i++];} else {� _i = _iterator.next();if (_i.done) break;� _ref = _i.value;}var x = _ref;� s += x;}

return s;}

es2015-loose

71 chars

421 chars

Proprietary

18 of 52

async function all(c, a) {for (const f of c) a = await f(a);return a;}

var all=function(){var b=_asyncToGenerator(regeneratorRuntime.mark(function d(e,g){var h,i,j,k,l,m;return regeneratorRuntime.wrap(function(o){for(;;)switch(o.prev=o.next){case 0:h=!0,i=!1,j=void 0,o.prev=3,k=e[Symbol.iterator]();case 5:if(h=(l=k.next()).done){o.next=13;break}return m=l.value,o.next=9,m(g);case 9:g=o.sent;case 10:h=!0,o.next=5;break;case 13:o.next=19;break;case 15:o.prev=15,o.t0=o["catch"](3),i=!0,j=o.t0;case 19:o.prev=19,o.prev=20,!h&&k.return&&k.return();case 22:if(o.prev=22,!i){o.next=25;break}throw j;case 25:return o.finish(22);case 26:return o.finish(19);case 27:return o.abrupt("return",g);case 28:case"end":return o.stop();}},d,this,[[3,15,19,27],[20,,22,26]])}));return function(){return b.apply(this,arguments)}}();

+

function _asyncToGenerator(b){return function(){var d=b.apply(this,arguments);return new Promise(function(e,g){function h(i,j){try{var k=d[i](j),l=k.value}catch(m){return void g(m)}return k.done?void e(l):Promise.resolve(l).then(function(m){h("next",m)},function(m){h("throw",m)})}return h("next")})}}

+

Proprietary

19 of 52

async function all(c, a) {for (const f of c) a = await f(a);return a;}

var all=function(){var b=_asyncToGenerator(regeneratorRuntime.mark(function d(e,g){var h,i,j,k,l,m;return regeneratorRuntime.wrap(function(o){for(;;)switch(o.prev=o.next){case 0:h=!0,i=!1,j=void 0,o.prev=3,k=e[Symbol.iterator]();case 5:if(h=(l=k.next()).done){o.next=13;break}return m=l.value,o.next=9,m(g);case 9:g=o.sent;case 10:h=!0,o.next=5;break;case 13:o.next=19;break;case 15:o.prev=15,o.t0=o["catch"](3),i=!0,j=o.t0;case 19:o.prev=19,o.prev=20,!h&&k.return&&k.return();case 22:if(o.prev=22,!i){o.next=25;break}throw j;case 25:return o.finish(22);case 26:return o.finish(19);case 27:return o.abrupt("return",g);case 28:case"end":return o.stop();}},d,this,[[3,15,19,27],[20,,22,26]])}));return function(){return b.apply(this,arguments)}}();

+

function _asyncToGenerator(b){return function(){var d=b.apply(this,arguments);return new Promise(function(e,g){function h(i,j){try{var k=d[i](j),l=k.value}catch(m){return void g(m)}return k.done?void e(l):Promise.resolve(l).then(function(m){h("next",m)},function(m){h("throw",m)})}return h("next")})}}

+

57 chars

720 chars

289 chars

2.1KiB

17.8KiB

Proprietary

20 of 52

JavaScript Transfer Size

426kB

of JavaScript on average!

Proprietary

Proprietary

21 of 52

15-30%*

Transpilation overhead!

* estimate based on my experiments

Proprietary

22 of 52

...not just transfer cost

Proprietary

23 of 52

...not just transfer cost

Proprietary

24 of 52

JavaScript engines optimize patterns

Proprietary

Proprietary

25 of 52

Traditional patterns

  • Simple syntactic patterns�typeof o === "number"
  • Integer arithmetic�x + y * 2
  • Monomorphic property access�var x = o.xo.x = x
  • Array element access�a[i + 1] = a[i]

Proprietary

26 of 52

Fast array iteration

Proprietary

27 of 52

function sum(a) {let s = 0;for (let x of a) s += x;return s;}

function sum(a) {var s = 0;var _iteratorNormalCompletion = true;var _didIteratorError = false;var _iteratorError = undefined;try {for (var _iterator = a[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {var x = _step.value;s += x;}} catch (err) {_didIteratorError = true;_iteratorError = err;} finally {try {if (!_iteratorNormalCompletion && _iterator.return) {_iterator.return();}} finally {if (_didIteratorError) {throw _iteratorError;}}}return s;}

Proprietary

28 of 52

function sum(a) {let s = 0;for (let x of a) s += x;return s;}

function sum(a) {var s = 0;var _iteratorNormalCompletion = true;var _didIteratorError = false;var _iteratorError = undefined;try {for (var _iterator = a[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {var x = _step.value;s += x;}} catch (err) {� _didIteratorError = true;� _iteratorError = err;} finally {try {if (!_iteratorNormalCompletion && _iterator.return) {� _iterator.return();}} finally {if (_didIteratorError) {throw _iteratorError;}}}return s;}

Proprietary

29 of 52

for (var _iterator = a[Symbol.iterator](), _step;

!(_step = _iterator.next()).done; ) {var x = _step.value;s += x;}

  • _iterator doesn’t escape
  • _step doesn’t escape

Array.prototype[Symbol.iterator]

%ArrayIteratorPrototype%.next

ToBoolean(Get(IterResultObject::done))

Get(IterResultObject::value)

🎉 Fast array iteration! 🎉

Proprietary

30 of 52

Compared to naive ES5

function sum(a) {let s = 0;for (let x of a) s += x;return s;}

function sum(a) {var s = 0;for (var i = 0; i < a.length; ++i) s += a[i];

return s;}

Proprietary

31 of 52

Performance cliffs

Proprietary

Proprietary

32 of 52

Proprietary

33 of 52

Proprietary

34 of 52

Proprietary

35 of 52

Proprietary

36 of 52

Symbol.hasInstance vs instanceof 😞

  • Symbol.hasInstance made instanceof monkey-patchable
  • Global guard to retain instanceof fast-path
    • Install Symbol.hasInstance property anywhere disables fast-path globally for the VM
    • All further instanceof take slow-path

fast

slow

Proprietary

37 of 52

Symbol.hasInstance vs instanceof 😄

  • Avoid global protector
  • Fast-path for o instanceof Foo
    • Foo is (known) constructor
    • Fold Foo[Symbol.hasInstance] during optimized compilation

Proprietary

38 of 52

Surprising footgun

console.time('replace');var str = 'abc';for (var i = 0; i < 1e7; ++i) {

str = str.replace('a', 'b');

}�console.timeEnd('replace');

import "babel-polyfill";

console.time('replace');var str = 'abc';for (var i = 0; i < 1e7; ++i) {

str = str.replace('a', 'b');

}�console.timeEnd('replace');

vs.

818ms

175ms

Proprietary

39 of 52

Simplified footgun

console.time('replace');var str = 'abc';for (var i = 0; i < 1e7; ++i) {str = str.replace('a', 'b');}console.timeEnd('replace');

String.prototype.unused = () => 0;��console.time('replace');var str = 'abc';for (var i = 0; i < 1e7; ++i) {str = str.replace('a', 'b');}console.timeEnd('replace');

vs.

791ms

175ms

Proprietary

40 of 52

Symbol.replace vs String.prototype.replace

slow

Proprietary

41 of 52

Proprietary

42 of 52

Fast ES2015+

Proprietary

Proprietary

43 of 52

Proprietary

44 of 52

Slowdown ES2015+ vs ES5 (Six-Speed)

Proprietary

45 of 52

Driving ESnext performance further

  • No meaningful (synthetic) ES2015+ benchmark — yet
  • Focus on real ES2015+ workloads (Babel, Webpack, TypeScript)
  • Big players considering to ship ES2015

Proprietary

46 of 52

WYWIWYS

“What you write is what you ship”

A.K.A. “transpile less”

Proprietary

47 of 52

babel-preset-env

npm install babel-preset-env --save-dev

“A Babel preset that can automatically determine the Babel plugins and polyfills you need based on your supported environments.”

Proprietary

Proprietary

48 of 52

tsc --target ES2015

tsc --target ES2016

tsc --target ES2017

tsc --target ESNext

Proprietary

49 of 52

Migrate to ES2015 via modules

Every browser that supports modules supports ES2015 (> 95%)

<script nomodule src="old.js"></script><script type="module" src="new.js"></script>

Proprietary

50 of 52

What’s next?

Take our JavaScript Language Survey

bit.ly/v8langsurvey

Proprietary

51 of 52

Takeaways

  • JavaScript engines optimize patterns (there’ll always be cliffs)
  • ES2015+ performance catching up
  • Transpile less!

Proprietary

52 of 52

The End

Proprietary

Proprietary