摘要:最后,客戶端只是依賴于引擎的環(huán)境之一。新的編譯器管道利用來(lái)實(shí)現(xiàn),并生成可以轉(zhuǎn)換生成器控制流到簡(jiǎn)單的本地控制流的字節(jié)碼??梢愿菀椎貎?yōu)化所得到的字節(jié)碼,因?yàn)樗恍枰狸P(guān)于生成器控制流的任何具體內(nèi)容,只是如何保存和恢復(fù)函數(shù)的狀態(tài)。
本文轉(zhuǎn)載自:眾成翻譯
譯者:smartsrh
鏈接:http://www.zcfy.cc/article/2978
原文:https://v8project.blogspot.sg/2017/02/high-performance-es2015-and-beyond.html
在過(guò)去的幾個(gè)月中,V8 團(tuán)隊(duì)一直努力讓新增的 ES2015 和其它更前沿的 JavaScript 功能的性能達(dá)到等效的 ES5的水平。
動(dòng)機(jī)在我們?cè)敿?xì)介紹各種改進(jìn)之前,我們首先應(yīng)該考慮為什么 ES2015+ 功能的性能很重要,盡管 Babel 在現(xiàn)代 Web 開發(fā)中得到廣泛的應(yīng)用:
首先,有的 ES2015 功能是按需解析成 ES5 的,例如內(nèi)置的 Object.assign 。 當(dāng) Babel 編譯對(duì)象擴(kuò)展語(yǔ)法(應(yīng)用在大量 React 和 Redux 程序)并且編譯器也支持這個(gè)語(yǔ)法時(shí),Babel 會(huì)使用 Object.assign 而棄用等效的 ES5 代碼。
將 ES2015 功能解析成 ES5 通常會(huì)增加大量代碼,加劇了當(dāng)前的 Web 性能危機(jī),尤其不利于新興市場(chǎng)上常見(jiàn)的千元機(jī)。因此,即使在考慮實(shí)際執(zhí)行成本之前,傳輸、解析和編譯代碼的成本就相當(dāng)高。
最后,客戶端JavaScript只是依賴于V8引擎的環(huán)境之一。 還有用于服務(wù)器端應(yīng)用程序和工具的 Node.js,開發(fā)人員不需要將代碼解析成 ES5,可以直接使用目標(biāo) Node.js 版本中相關(guān) V8 版本支持的功能。
讓我們考慮以下節(jié)選自 Redux 文檔中的代碼段:
function todoApp(state = initialState, action) { switch (action.type) { case SET_VISIBILITY_FILTER: return { ...state, visibilityFilter: action.filter } default: return state } }
該代碼中有兩處需要解析成 ES5:state 的默認(rèn)參數(shù)和 state 的擴(kuò)展對(duì)象語(yǔ)法。Babel 生成以下 ES5 代碼:
"use strict"; var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; function todoApp() { var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : initialState; var action = arguments[1]; switch (action.type) { case SET_VISIBILITY_FILTER: return _extends({}, state, { visibilityFilter: action.filter }); default: return state; } }
現(xiàn)在假如 Object.assign比 Babel 生成的 polyfilled_extends 要慢好幾個(gè)數(shù)量級(jí)。在這種情況下,從不支持 Object.assign 的瀏覽器升級(jí)到支持 ES2015 的瀏覽器版本將大幅降低性能,可能會(huì)阻礙 ES2015 的普及。
此示例還體現(xiàn)了解析成 ES5 的另一個(gè)重要缺點(diǎn):發(fā)送給用戶的代碼通常遠(yuǎn)大于開發(fā)人員最初編寫的 ES2015+ 代碼。在上面的示例中,原始代碼是 203 字符(gzip 壓縮后 176 字節(jié)),而生成的代碼是 588 字符(gzip 壓縮后 367 字節(jié))。體積增長(zhǎng)了兩倍。 我們來(lái)看看 Async Iterators for JavaScript 的另一個(gè)例子:
async function* readLines(path) { let file = await fileOpen(path); try { while (!file.EOF) { yield await file.readLine(); } } finally { await file.close(); } }
Babel 將以上 187 字符(gzip 壓縮后 150 字節(jié))解析成 2987 字符的 ES5 代碼(gzip 壓縮后 971 字節(jié)),這里還沒(méi)考慮所需依賴的 regenerator runtime :
"use strict"; var _asyncGenerator = function () { function AwaitValue(value) { this.value = value; } function AsyncGenerator(gen) { var front, back; function send(key, arg) { return new Promise(function (resolve, reject) { var request = { key: key, arg: arg, resolve: resolve, reject: reject, next: null }; if (back) { back = back.next = request; } else { front = back = request; resume(key, arg); } }); } function resume(key, arg) { try { var result = gen[key](arg); var value = result.value; if (value instanceof AwaitValue) { Promise.resolve(value.value).then(function (arg) { resume("next", arg); }, function (arg) { resume("throw", arg); }); } else { settle(result.done ? "return" : "normal", result.value); } } catch (err) { settle("throw", err); } } function settle(type, value) { switch (type) { case "return": front.resolve({ value: value, done: true }); break; case "throw": front.reject(value); break; default: front.resolve({ value: value, done: false }); break; } front = front.next; if (front) { resume(front.key, front.arg); } else { back = null; } } this._invoke = send; if (typeof gen.return !== "function") { this.return = undefined; } } if (typeof Symbol === "function" && Symbol.asyncIterator) { AsyncGenerator.prototype[Symbol.asyncIterator] = function () { return this; }; } AsyncGenerator.prototype.next = function (arg) { return this._invoke("next", arg); }; AsyncGenerator.prototype.throw = function (arg) { return this._invoke("throw", arg); }; AsyncGenerator.prototype.return = function (arg) { return this._invoke("return", arg); }; return { wrap: function wrap(fn) { return function () { return new AsyncGenerator(fn.apply(this, arguments)); }; }, await: function await(value) { return new AwaitValue(value); } }; }(); var readLines = function () { var _ref = _asyncGenerator.wrap(regeneratorRuntime.mark(function _callee(path) { var file; return regeneratorRuntime.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: _context.next = 2; return _asyncGenerator.await(fileOpen(path)); case 2: file = _context.sent; _context.prev = 3; case 4: if (file.EOF) { _context.next = 11; break; } _context.next = 7; return _asyncGenerator.await(file.readLine()); case 7: _context.next = 9; return _context.sent; case 9: _context.next = 4; break; case 11: _context.prev = 11; _context.next = 14; return _asyncGenerator.await(file.close()); case 14: return _context.finish(11); case 15: case "end": return _context.stop(); } } }, _callee, this, [[3,, 11, 15]]); })); return function readLines(_x) { return _ref.apply(this, arguments); }; }();
代碼體積增加了 650%(_asyncGenerator 函數(shù)是可復(fù)用的,具體取決于捆綁代碼的方式,因此可以在多個(gè)異步迭代器使用中減小一些代碼的體積)。我們不認(rèn)為將代碼解析成 ES5 可以解決所有問(wèn)題,因?yàn)榇a體積的增加不僅會(huì)影響下載時(shí)間/成本,還會(huì)增加解析和編譯的額外開銷。如果我們真的想大幅度地改善現(xiàn)代 Web 應(yīng)用程序的頁(yè)面加載和緩存(特別是在移動(dòng)設(shè)備上)的效率,我們必須鼓勵(lì)開發(fā)人員在編寫代碼時(shí)不僅使用 ES2015+,并且不需解析成 ES5 就直接發(fā)送給客戶端,只向不支持 ES2015 的傳統(tǒng)瀏覽器提供完全解析的代碼。對(duì)于編譯器的作者而言,這一想法意味著我們需要直接支持 ES2015+ 功能,并提供合理的性能。
測(cè)試方法如上所述,ES2015+ 功能的絕對(duì)性能并不是主要矛盾。相反,目前應(yīng)優(yōu)先確保 ES2015+ 功能的性能與等效的原生 ES5 代碼相當(dāng),更重要的是和 Babel 生成的代碼性能相當(dāng)。Kevin Decker 有一個(gè)項(xiàng)目叫 six-speed,它或多或少可以滿足我們的需求:ES2015 功能與等效的原生 ES5 代碼與解析后產(chǎn)生的 ES5 代碼之間的性能比較。
所以我們決定用它作為我們開始 ES2015+ 性能工作的基礎(chǔ)。我們拷貝了該項(xiàng)目并添加了一些測(cè)試。 我們首先關(guān)注性能最差的部分,比如說(shuō)列表項(xiàng),原生的 ES5 比 ES2015+ 版本效率高 2 倍,因?yàn)槲覀兊幕炯僭O(shè)是原生的 ES5 版本至少與 Babel 的版本一樣快。
一個(gè)為現(xiàn)代語(yǔ)言而生的現(xiàn)代架構(gòu)過(guò)去,V8 很難改善 ES2015+ 功能的優(yōu)化,例如,給 Crankshaft —— V8 的經(jīng)典優(yōu)化編譯器—— 添加異常處理(比如 try/catch/finally)是不可行的。 這意味著 V8 優(yōu)化 ES6 功能像 for...of 之類的的能力是有限的,因?yàn)樗举|(zhì)上是一個(gè)隱含的 finally 子句。Crankshaft 的局限性以及將全新的語(yǔ)言功能添加到全代碼(V8 的基準(zhǔn)編譯器)中的整體復(fù)雜性,使得 V8 難添加和優(yōu)化剛剛標(biāo)準(zhǔn)化的新 ES 功能。
幸運(yùn)的是,V8 的新的解釋器 Ignition 和編譯器管道 TurboFan 從一開始就著手支持整個(gè) JavaScript 語(yǔ)言,包括高級(jí)控制流程,異常處理以及 ES2015 的最新版本和解構(gòu)賦值。Ignition 和 TurboFan 架構(gòu)的緊密結(jié)合可以快速添加新功能并逐步進(jìn)行優(yōu)化。
對(duì)于許多現(xiàn)代的 ES 功能和改進(jìn)只有在新的 Ignition 和 TurboFan 下才可行。 Ignition 和 TurboFan 對(duì)于優(yōu)化生成器和 async 尤其重要。生成器早已得到 V8 的支持,但由于 Crankshaft 控制流的限制而不能進(jìn)一步得到優(yōu)化。async 基本上是生成器的語(yǔ)法糖,因此屬于同一類別。新的編譯器管道利用 Ignition 來(lái)實(shí)現(xiàn) AST,并生成可以轉(zhuǎn)換生成器控制流到簡(jiǎn)單的本地控制流的字節(jié)碼。TurboFan 可以更容易地優(yōu)化所得到的字節(jié)碼,因?yàn)樗恍枰狸P(guān)于生成器控制流的任何具體內(nèi)容,只是如何保存和恢復(fù)函數(shù)的 yield 狀態(tài)。
小組的狀況我們的短期目標(biāo)是讓效率差距盡快縮減到 2 倍以內(nèi)。我們首先改進(jìn)測(cè)試成績(jī)最差的功能,從 Chrome M54 到 Chrome M58(Canary),我們已經(jīng)成功將測(cè)試速度降了一倍,從 16 降至 8 ,同時(shí) M54 中最差的 19 倍在 M58(Canary)減少到了只有 6 倍。與此同時(shí),我們也大大減少了效率差距的平均和中位數(shù):
可以看到 ES2015+ 和 ES5 正在接近的趨勢(shì)。我們把平均性能提高到了 ES5 的 47% 以上。 以下是自 M54 以來(lái)我們做的一些亮點(diǎn)。
最值得注意的是,我們改進(jìn)了基于迭代的新語(yǔ)言結(jié)構(gòu)的性能,如擴(kuò)展運(yùn)算符,解構(gòu)賦值和 for...of 循環(huán)。例如,使用數(shù)組解構(gòu)賦值
function fn() { var [c] = data; return c; }
和原生的 ES5 賦值語(yǔ)句效率相當(dāng)
function fn() { var c = data[0]; return c; }
比 babel 生成的代碼快多了:
"use strict"; var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); function fn() { var _data = data, _data2 = _slicedToArray(_data, 1), c = _data2[0]; return c; }
想了解更多詳細(xì)信息可以在上次慕尼黑 NodeJS 用戶組會(huì)議上查看我們提供的高效 ES2015 演講:
我們致力于繼續(xù)提高 ES2015+ 的性能。如果對(duì)這些細(xì)節(jié)感興趣,請(qǐng)查看 V8 的 ES2015 及其未來(lái)的性能計(jì)劃。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/84941.html
摘要:類將源代碼解釋并構(gòu)建成抽象語(yǔ)法樹,使用類來(lái)創(chuàng)建它們,并使用類來(lái)分配內(nèi)存。類抽象語(yǔ)法樹的訪問(wèn)者類,主要用來(lái)遍歷抽象語(yǔ)法樹。在該函數(shù)中,先使用類來(lái)生成抽象語(yǔ)法樹再使用類來(lái)生成本地代碼。 通過(guò)上一篇文章,我們知道了JavaScript引擎是執(zhí)行JavaScript代碼的程序或解釋器,了解了JavaScript引擎的基本工作原理。我們經(jīng)常聽說(shuō)的JavaScript引擎就是V8引擎,這篇文章我們...
摘要:歡迎來(lái)我的博客閱讀開發(fā)者所需要知道的一是一款擁有自動(dòng)垃圾回收功能的編程語(yǔ)言。它隨著的第一版發(fā)布而發(fā)布以及開源。年月,基金宣布和合并,合并版本在未來(lái)發(fā)布。年月日,官方公布又一個(gè)新的名為的優(yōu)化編譯器,主要提供的新語(yǔ)法,以及提高性能。 歡迎來(lái)我的博客閱讀:「JavaScript 開發(fā)者所需要知道的 V8(一):V8 In NodeJS」 Motivation JavaScript 是一款擁有...
摘要:所有功能分為三組,用于交付階段和進(jìn)行中的功能認(rèn)為穩(wěn)定的所有交付功能在上默認(rèn)打開,不需要任何類型的運(yùn)行時(shí)標(biāo)志。及更高版本引入的優(yōu)化功能的工作通過(guò)性能計(jì)劃進(jìn)行協(xié)調(diào),團(tuán)隊(duì)收集并協(xié)調(diào)需要改進(jìn)的領(lǐng)域,并設(shè)計(jì)文檔來(lái)解決這些問(wèn)題。 ECMAScript 2015(ES6)及更高版本 Node.js是針對(duì)現(xiàn)代版本的V8構(gòu)建的,通過(guò)與該引擎的最新版本保持同步,我們確保及時(shí)向Node.js開發(fā)人員提供Ja...
摘要:的新特性往往會(huì)增加代碼的,這些特性卻有助于緩解當(dāng)前的性能危機(jī),尤其像在手機(jī)設(shè)備這樣的新興市場(chǎng)上。聯(lián)合聲明我們短期目標(biāo)是盡快實(shí)現(xiàn)少于倍的性能改善。我們會(huì)繼續(xù)針對(duì)的特性提升其性能。定期發(fā)布高質(zhì)量文章。 作者:Alon Zakai 編譯:胡子大哈 翻譯原文:http://huziketang.com/blog/posts/detail?postId=58d11a9aa6d8a07e449f...
摘要:對(duì)于每個(gè)前端程序員來(lái)講都有一個(gè)終極理想,那就是搞懂引擎是如何工作的。性能經(jīng)過(guò)了兩次飛躍第次飛躍是年發(fā)布,第次則是年的。從去年底開始連載源碼分析,記錄一下自己學(xué)習(xí)源碼的點(diǎn)點(diǎn)滴滴。月星期六晚點(diǎn)和大家一起聊聊引擎前端程序員應(yīng)該懂點(diǎn)知識(shí)講堂。 對(duì)于每個(gè)前端程序員來(lái)講都有一個(gè)終極理想,那就是搞懂 javascript 引擎是如何工作的。 從我的網(wǎng)絡(luò) ID(justjavac)可以看出來(lái),當(dāng)我開始...
閱讀 765·2021-09-28 09:35
閱讀 2598·2019-08-29 11:25
閱讀 2161·2019-08-23 18:36
閱讀 1861·2019-08-23 16:31
閱讀 2076·2019-08-23 14:50
閱讀 3126·2019-08-23 13:55
閱讀 3297·2019-08-23 12:49
閱讀 2088·2019-08-23 11:46