摘要:的新特性往往會(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=58d11a9aa6d8a07e449fdd2a
英文原文:High-performance ES2015 and beyond
轉(zhuǎn)載請(qǐng)注明出處,保留原文鏈接以及作者信息
過(guò)去幾個(gè)月 V8 團(tuán)隊(duì)聚焦于提升新增的 ES2015 的一些性能、提升最近一些其他 JavaScript 新特性的性能,使其能夠達(dá)到或超越相應(yīng)的 ES5 的性能。
出發(fā)點(diǎn)在我們討論這些不同的改進(jìn)之前,要先了解在當(dāng)前的 Web 開(kāi)發(fā)中,已經(jīng)有了廣為使用的 Babel 作為編譯器,為什么還要考慮 ES2015+ 的性能問(wèn)題:
首先,有一些新的 ES2015 特性是只有 polyfill 時(shí)需要的。例如 Object.assign 函數(shù)。當(dāng) Babel 轉(zhuǎn)譯 “object spread property” 的時(shí)候(在 React 和 Redux 中經(jīng)常碰到),就會(huì)依賴(lài) Object.assign 來(lái)替代 ES5 中相應(yīng)的函數(shù)(如果VM環(huán)境支持的話(huà))。
polyfill ES2015 的新特性往往會(huì)增加代碼的 size,這些 ES2015 特性卻有助于緩解當(dāng)前的 web 性能危機(jī),尤其像在手機(jī)設(shè)備這樣的新興市場(chǎng)上。在這樣一種情況下,代碼的解析和的成本將會(huì)很高。
最后,客戶(hù)端的 JavaScript 運(yùn)行環(huán)境只是依賴(lài)于 V8 引擎的環(huán)境之一,還有服務(wù)端的 Node.js 應(yīng)用和工具等,它們都不需要轉(zhuǎn)譯成 ES5 代碼,而直接使用最新的 V8 版本就可以使用這些新特性了。
一起來(lái)看一下下面這段 Redux 文檔中的代碼:
function todoApp(state = initialState, action) { switch (action.type) { case SET_VISIBILITY_FILTER: return { ...state, visibilityFilter: action.filter } default: return state } }
有兩個(gè)地方需要轉(zhuǎn)譯:默認(rèn)參數(shù) state 和 state 作為實(shí)例化對(duì)象進(jìn)行返回。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; } }
假設(shè) Object.assign 要比用 Babel polyfill 生成的代碼要慢一個(gè)數(shù)量級(jí)。這樣的情況下,要將一個(gè)本不支持 Object.assign 的瀏覽器優(yōu)化到使它具有 ES2015 能力,會(huì)引起很?chē)?yán)重的性能問(wèn)題。
這個(gè)例子同時(shí)也指出了轉(zhuǎn)譯的另一個(gè)缺點(diǎn):轉(zhuǎn)譯生成的代碼,要比直接用 ES2015+ 寫(xiě)的代碼體積更大。在上面的例子中,源代碼有 203 個(gè)字符(gzip 壓縮后有 176 字節(jié)),而轉(zhuǎn)譯生成的代碼有 588 個(gè)字符(gzip 壓縮后有 367 字節(jié))。代碼大小是原來(lái)的兩倍。下面來(lái)看關(guān)于 “JavaScript 異步迭代器”的一個(gè)例子:
async function* readLines(path) { let file = await fileOpen(path); try { while (!file.EOF) { yield await file.readLine(); } } finally { await file.close(); } }
Babel 轉(zhuǎn)譯這段 187 個(gè)字符(gzip 壓縮后 150 字節(jié)),會(huì)生成一段有 2987 個(gè)字符(gzip 壓縮后 971 字節(jié))的 ES5 代碼,這還不包括再生器運(yùn)行時(shí)需要加載的額外依賴(lài):
"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); }; }();
這段代碼的大小是原來(lái)的 6.5 倍,也就是說(shuō)增長(zhǎng)了 650% (生成的 _asyncGenerator 函數(shù)也可能被共享,不過(guò)這依賴(lài)于你如何打包你的代碼。如果被共享的話(huà),多個(gè)異步迭代器共用會(huì)分?jǐn)偞a大小帶來(lái)的成本)。我們認(rèn)為長(zhǎng)遠(yuǎn)來(lái)看一直通過(guò)轉(zhuǎn)譯的方式來(lái)支持 ES5 是不可行的,代碼 size 的增加不僅僅會(huì)使下載的時(shí)間變長(zhǎng),而且也會(huì)增加解析和編譯的開(kāi)銷(xiāo)。如果我們想要徹底改善頁(yè)面加載速度,和移動(dòng)互聯(lián)網(wǎng)應(yīng)用的反應(yīng)速度(尤其在手機(jī)設(shè)備上),那么一定要鼓勵(lì)開(kāi)發(fā)者使用 ES2015+ 來(lái)開(kāi)發(fā),而不是開(kāi)發(fā)完以后轉(zhuǎn)譯成 ES5。對(duì)于不支持 ES2015 的舊瀏覽器,只有給它們完全轉(zhuǎn)譯以后的代碼去執(zhí)行了,而對(duì)于 VM 系統(tǒng),上面所說(shuō)的這個(gè)愿景也要求我們不斷地提升 ES2015 的性能。
評(píng)估方法正如上面所說(shuō)的,ES2015+ 自身的絕對(duì)性能現(xiàn)在已經(jīng)不是關(guān)鍵了。當(dāng)前的關(guān)鍵是首先一定要確保 ES2015+ 的性能要比純 ES5 高,第二更重要的是一定要比用 Babel 轉(zhuǎn)譯以后的版本性能高。目前已經(jīng)有了一個(gè)由 Kevin Decker 開(kāi)發(fā)的 six-speed 項(xiàng)目,這個(gè)項(xiàng)目多多少少實(shí)現(xiàn)了我們的需求:ES2015 特性 vs 純 ES5 vs 轉(zhuǎn)譯生成代碼三者之間的比較。
因此我們現(xiàn)在把提升相對(duì)性能作為我們做 ES2015+ 性能提升的基礎(chǔ)。首先將會(huì)把注意力聚焦于那些最嚴(yán)重的問(wèn)題上,即上面圖中所列出的,從純 ES5 所對(duì)應(yīng)的 ES2015+ 版本性能下降 2 倍的那些項(xiàng)。之所以這么說(shuō)是因?yàn)橛袀€(gè)前提假設(shè),假設(shè)純 ES5 的版本至少會(huì)和相應(yīng) Babel 生成的版本速度一樣快。
為現(xiàn)代語(yǔ)言而生的現(xiàn)代架構(gòu)以前版本的 V8 優(yōu)化像 ES2015+ 這樣的語(yǔ)言是比較困難的。比如想要加一個(gè)異常處理(即 try/chtch/finally)到 Crankshaft (V8 以前版本的優(yōu)化編譯器)是不可能的。就是說(shuō)以 V8 的能力去優(yōu)化 ES6 中的 for...of (這里面隱含有 finally 語(yǔ)句)都是有問(wèn)題的。Crankshaft 在增加新的語(yǔ)言特性到編譯器方面有很多局限性和實(shí)現(xiàn)的復(fù)雜性,這就使得 V8 框架的更新優(yōu)化速度很難跟得上 ES 標(biāo)準(zhǔn)化的速度。拖慢了 V8 發(fā)展的節(jié)奏。
幸運(yùn)的是,lgnition 和 TurboFan (V8 的新版解釋器和編譯器)在設(shè)計(jì)之初就考慮支持整個(gè) JavaScript 語(yǔ)言體系。包括先進(jìn)的控制流、異常處理、最近的 for...of 特性和 ES2015 的重構(gòu)等。lgnition 和 TurboFan 的密集組合架構(gòu)使得對(duì)于新特性的整體優(yōu)化和增量式優(yōu)化成為可能。
許多我們已經(jīng)在現(xiàn)代語(yǔ)言特性上所取得的成功只有在 lgnition/TurboFan 上才可能實(shí)現(xiàn)。 lgnition/TurboFan 在優(yōu)化生成器和異步函數(shù)方面的設(shè)計(jì)尤其關(guān)鍵。V8 一直以來(lái)都支持生成器,但是由于 Crankshaft 的限制,對(duì)其優(yōu)化會(huì)極其受限。新的編譯器利用 lgnition 生成字節(jié)碼,這可以使復(fù)雜的生成器控制流轉(zhuǎn)化為簡(jiǎn)單的本地字節(jié)控制流。TurboFan 也可以更容易實(shí)現(xiàn)基于字節(jié)流的優(yōu)化,因?yàn)樗灰郎善骺刂屏鞯奶厥饧?xì)節(jié),只需要知道如何保存和恢復(fù)函數(shù)聲明就可以了。
聯(lián)合聲明我們短期目標(biāo)是盡快實(shí)現(xiàn)少于 2 倍的性能改善。首先從最差情況的實(shí)驗(yàn)開(kāi)始,從 Chrome M54 到 Chrome M58 我們成功的把慢于 2 倍的測(cè)試集從 16 個(gè)降到了 8 個(gè)。同時(shí)也顯著地使緩慢程度的中位數(shù)和平均數(shù)得以降低。
從下圖中我們可以清晰地看到變化趨勢(shì),已經(jīng)實(shí)現(xiàn)了平均性能超過(guò)了 ES5 大概 47%,這里列出的是在 M54 上的一些典型數(shù)據(jù)。
另外我們顯著提高了基于迭代的新語(yǔ)言的性能,例如傳遞操作符和 for...of 循環(huán)等。下面是一個(gè)數(shù)組的重構(gòu)情況:
function fn() { var [c] = data; return c; }
比純 ES5 版本還要快。ES5:
function fn() { var c = data[0]; return c; }
比 Babel 生成的代碼要快的更多。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; }
你可以到“高速 ES2015” 來(lái)了解更多細(xì)節(jié)的信息。下面這里是我們?cè)?2017 年 1 月 12 日發(fā)出的視頻連接。
我們會(huì)繼續(xù)針對(duì) ES2015+ 的特性提升其性能。如果你對(duì)這一問(wèn)題感興趣,請(qǐng)看我們 V8 的“ES2015 and beyond performance plan。
如果大家對(duì)文章感興趣,歡迎關(guān)注我的知乎專(zhuān)欄-前端大哈。定期發(fā)布高質(zhì)量文章。
我最近正在寫(xiě)一本《React.js 小書(shū)》,對(duì) React.js 感興趣的童鞋,歡迎指點(diǎn)。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/86921.html
摘要:還記得剛開(kāi)始學(xué)習(xí)的時(shí)候,內(nèi)存管理前端掘金作為一門(mén)高級(jí)語(yǔ)言,并不像低級(jí)語(yǔ)言那樣擁有對(duì)內(nèi)存的完全掌控。第三方庫(kù)的行代碼內(nèi)實(shí)現(xiàn)一個(gè)前端掘金前言本文會(huì)教你如何在行代碼內(nèi),不依賴(lài)任何第三方的庫(kù),用純實(shí)現(xiàn)一個(gè)。 (譯) 如何使用 JavaScript 構(gòu)建響應(yīng)式引擎 —— Part 1:可觀察的對(duì)象 - 掘金原文地址:How to build a reactive engine in JavaSc...
摘要:還記得剛開(kāi)始學(xué)習(xí)的時(shí)候,內(nèi)存管理前端掘金作為一門(mén)高級(jí)語(yǔ)言,并不像低級(jí)語(yǔ)言那樣擁有對(duì)內(nèi)存的完全掌控。第三方庫(kù)的行代碼內(nèi)實(shí)現(xiàn)一個(gè)前端掘金前言本文會(huì)教你如何在行代碼內(nèi),不依賴(lài)任何第三方的庫(kù),用純實(shí)現(xiàn)一個(gè)。 (譯) 如何使用 JavaScript 構(gòu)建響應(yīng)式引擎 —— Part 1:可觀察的對(duì)象 - 掘金原文地址:How to build a reactive engine in JavaSc...
摘要:最后,客戶(hù)端只是依賴(lài)于引擎的環(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.blo...
摘要:從最開(kāi)始的到封裝后的都在試圖解決異步編程過(guò)程中的問(wèn)題。為了讓編程更美好,我們就需要引入來(lái)降低異步編程的復(fù)雜性。異步編程入門(mén)的全稱(chēng)是前端經(jīng)典面試題從輸入到頁(yè)面加載發(fā)生了什么這是一篇開(kāi)發(fā)的科普類(lèi)文章,涉及到優(yōu)化等多個(gè)方面。 TypeScript 入門(mén)教程 從 JavaScript 程序員的角度總結(jié)思考,循序漸進(jìn)的理解 TypeScript。 網(wǎng)絡(luò)基礎(chǔ)知識(shí)之 HTTP 協(xié)議 詳細(xì)介紹 HTT...
摘要:嚴(yán)肅的開(kāi)場(chǎng)白故事要從深度學(xué)習(xí)說(shuō)起。本文從視頻分類(lèi)的角度,對(duì)深度學(xué)習(xí)在該方向上的算法進(jìn)行總結(jié)。數(shù)據(jù)集熟悉深度學(xué)習(xí)的朋友們應(yīng)該清楚,深度學(xué)習(xí)是一門(mén)數(shù)據(jù)驅(qū)動(dòng)的技術(shù),因此數(shù)據(jù)集對(duì)于算法的研究起著非常重要的作用。是一個(gè)比較成功的傳統(tǒng)方法與深度學(xué)習(xí)算 showImg(https://segmentfault.com/img/bV7hQP?w=900&h=330); 不嚴(yán)肅的開(kāi)場(chǎng)白 視頻社交已經(jīng)成為...
閱讀 909·2021-09-09 09:32
閱讀 2849·2021-09-02 10:20
閱讀 2685·2021-07-23 11:24
閱讀 824·2019-08-30 15:54
閱讀 3631·2019-08-30 15:54
閱讀 1346·2019-08-30 11:02
閱讀 2844·2019-08-26 17:40
閱讀 1122·2019-08-26 13:55