亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

[譯] JavaScript 性能優(yōu)化殺手

MockingBird / 2649人閱讀

摘要:原文引言這篇文檔包含了如何避免使代碼性能遠(yuǎn)低于預(yù)期的建議尤其是一些會(huì)導(dǎo)致牽涉到等無(wú)法優(yōu)化相關(guān)函數(shù)的問題一些背景在中并沒有解釋器但卻有兩個(gè)不同的編譯器通用編譯器和優(yōu)化編譯器這意味著你的代碼總是會(huì)被編譯為機(jī)器碼后直接運(yùn)行這樣一定很快咯并不是

原文:http://dev.zm1v1.com/2015/08/19/javascript-optimization-killers/
引言

這篇文檔包含了如何避免使代碼性能遠(yuǎn)低于預(yù)期的建議. 尤其是一些會(huì)導(dǎo)致 V8 (牽涉到 Node.js, Opera, Chromium 等) 無(wú)法優(yōu)化相關(guān)函數(shù)的問題.

一些 V8 背景

在 V8 中并沒有解釋器, 但卻有兩個(gè)不同的編譯器: 通用編譯器和優(yōu)化編譯器. 這意味著你的 JavaScript 代碼總是會(huì)被編譯為機(jī)器碼后直接運(yùn)行. 這樣一定很快咯? 并不是. 僅僅是編譯為本地代碼并不能明顯提高性能. 它只是消除了解釋器的開銷, 但如果未被優(yōu)化, 代碼依舊很慢.

舉個(gè)例子, 使用通用編譯器, a + b 會(huì)變成這個(gè)樣子:

mov eax, a
mov ebx, b
call RuntimeAdd
換言之它僅僅是調(diào)用了運(yùn)行時(shí)的函數(shù). 如果 a 和 b 一定是整數(shù), 那可以像這樣:

mov eax, a
mov ebx, b
add eax, ebx
相比而言這會(huì)遠(yuǎn)快于調(diào)用需要處理復(fù)雜 JavaScript 運(yùn)行時(shí)語(yǔ)義的函數(shù).

通常來說, 通用編譯器得到的是第一種結(jié)果, 而優(yōu)化編譯器則會(huì)得到第二種結(jié)果. 使用優(yōu)化編譯器編譯的代碼可以很容易比通用編譯器編譯的代碼快上 100 倍. 但這里有個(gè)坑, 并非所有的 JavaScript 代碼都能被優(yōu)化. 在 JavaScript 中有很多種寫法, 包括具備語(yǔ)義的, 都不能被優(yōu)化編譯器編譯 (回落到通用編譯器*).

記下一些會(huì)導(dǎo)致整個(gè)函數(shù)無(wú)法使用優(yōu)化編譯器的用法很重要. 一次代碼優(yōu)化的是一整個(gè)函數(shù), 優(yōu)化過程中并不會(huì)關(guān)心其他代碼做了什么 (除非代碼在已經(jīng)被優(yōu)化的函數(shù)中).

這個(gè)指南會(huì)涵蓋多數(shù)會(huì)導(dǎo)致整個(gè)函數(shù)掉進(jìn) “反優(yōu)化火獄” 的例子. 由于編譯器一直在不斷更新, 未來當(dāng)它能夠識(shí)別下面的一些情況時(shí), 這里提到的處理方法可能也就不必要了.

索引

工具和方法
不支持的語(yǔ)法
使用 arguments
switch…case
for…in
無(wú)限循環(huán)

工具和方法

你可以通過添加一些 V8 標(biāo)記來使用 Node.js 驗(yàn)證不同的用法如何影響優(yōu)化結(jié)果. 通??梢詫懸粋€(gè)包含了特定用法的函數(shù), 使用所有可能的參數(shù)類型去調(diào)用它, 再使用 V8 的內(nèi)部函數(shù)去優(yōu)化和審查.

test.js

// 包含需要審查的用法的函數(shù) (這里是 with 語(yǔ)句)
function containsWith() {

return 3;
with({}) { }

}

function printStatus(fn) {

switch(%GetOptimizationStatus(fn)) {
    case 1: console.log("Function is optimized"); break;
    case 2: console.log("Function is not optimized"); break;
    case 3: console.log("Function is always optimized"); break;
    case 4: console.log("Function is never optimized"); break;
    case 6: console.log("Function is maybe deoptimized"); break;
}

}

// 告訴編譯器類型信息
containsWith();
// 為了使?fàn)顟B(tài)從 uninitialized 變?yōu)?pre-monomorphic, 再變?yōu)?monomorphic, 兩次調(diào)用是必要的
containsWith();

%OptimizeFunctionOnNextCall(containsWith);
// 下一次調(diào)用
containsWith();

// 檢查
printStatus(containsWith);
執(zhí)行:

$ node --trace_opt --trace_deopt --allow-natives-syntax test.js
Function is not optimized
作為是否被優(yōu)化的對(duì)比, 注釋掉 with 語(yǔ)句再來一次:

$ node --trace_opt --trace_deopt --allow-natives-syntax test.js
[optimizing 000003FFCBF74231 - took 0.345, 0.042, 0.010 ms]
Function is optimized
使用這個(gè)方法來驗(yàn)證處理方法有效且必要是很重要的.

不支持的語(yǔ)法

優(yōu)化編譯器不支持一些特定的語(yǔ)句, 使用這些語(yǔ)法會(huì)使包含它的函數(shù)無(wú)法得到優(yōu)化.

有一點(diǎn)請(qǐng)注意, 即使這些語(yǔ)句無(wú)法到達(dá)或者不會(huì)被執(zhí)行, 它們也會(huì)使相關(guān)函數(shù)無(wú)法被優(yōu)化.

比如這樣做是沒用的:

if (DEVELOPMENT) {

debugger;

}
上面的代碼會(huì)導(dǎo)致包含它的整個(gè)函數(shù)不被優(yōu)化, 即使從來不會(huì)執(zhí)行到 debugger 語(yǔ)句.

目前不會(huì)被優(yōu)化的有:

generator 函數(shù)
包含 for…of 語(yǔ)句的函數(shù)
包含 try…catch 的函數(shù)
包含 try…finally 的函數(shù)
包含復(fù)合 let 賦值語(yǔ)句的函數(shù) (原文為 compound let assignment)
包含復(fù)合 const 賦值語(yǔ)句的函數(shù) (原文為 compound const assignment)
包含含有 proto 或者 get/set 聲明的對(duì)象字面量的函數(shù)
可能永遠(yuǎn)不會(huì)被優(yōu)化的有:

包含 debugger 語(yǔ)句的函數(shù)
包含字面調(diào)用 eval() 的函數(shù)
包含 with 語(yǔ)句的函數(shù)
最后一點(diǎn)明確一下, 如果有下面任何的情況, 整個(gè)函數(shù)都無(wú)法被優(yōu)化:

function containsObjectLiteralWithProto() {

return { __proto__: 3 };

}
function containsObjectLiteralWithGetter() {

return {
    get prop() {
        return 3;
    }
};

}
function containsObjectLiteralWithSetter() {

return {
    set prop(val) {
        this.val = val;
    }
};

}
提一下直接使用 eval 和 with 的情況, 因?yàn)樗鼈儠?huì)造成相關(guān)嵌套的函數(shù)作用域變?yōu)閯?dòng)態(tài)的. 這樣一來則有可能也影響其他很多函數(shù), 因?yàn)檫@種情況下無(wú)法從詞法上判斷相關(guān)變量的有效范圍.

處理方法

之前提到過的一些語(yǔ)句在生產(chǎn)環(huán)境中是無(wú)法避免的, 比如 try...finally 和 try...catch. 為了是代價(jià)最小, 它們必須被隔離到一個(gè)最小化的函數(shù), 以保證主要的代碼不受影響.

var errorObject = { value: null };
function tryCatch(fn, ctx, args) {

try {
    return fn.apply(ctx, args);
} catch(e) {
    errorObject.value = e;
    return errorObject;
}

}

var result = tryCatch(mightThrow, void 0, [1,2,3]);
// 不帶歧義地判斷是否調(diào)用拋出了異常 (或其他值)
if(result === errorObject) {

var error = errorObject.value;

} else {

// 結(jié)果是返回值

}

使用 arguments

有不少使用 arguments 的方式會(huì)導(dǎo)致相關(guān)函數(shù)無(wú)法被優(yōu)化. 所以在使用 arguments 的時(shí)候需要非常留意.

3.1. 給一個(gè)已經(jīng)定義的參數(shù)重新賦值, 并且在相關(guān)語(yǔ)句主體中引用 (僅限非嚴(yán)格模式). 典型的例子:

function defaultArgsReassign(a, b) {

 if (arguments.length < 2) b = 5;

}
處理方法則是賦值該參數(shù)給一個(gè)新的變量:

function reAssignParam(a, b_) {

var b = b_;
// 與 b_ 不同, b 可以安全地被重新賦值
if (arguments.length < 2) b = 5;

}
如果僅僅是在這種情況下在函數(shù)中用到了 arguments, 也可以寫為是否為 undefined 的判斷:

function reAssignParam(a, b) {

if (b === void 0) b = 5;

}
然而如果之后這個(gè)函數(shù)中用到 arguments, 維護(hù)代碼的同學(xué)可能會(huì)容易忘掉要把重新賦值的語(yǔ)句留下**.

第二個(gè)處理方法: 對(duì)整個(gè)文件或者函數(shù)開啟嚴(yán)格模式 ("use strict").

3.2. 泄露 arguments:

function leaksArguments1() {

return arguments;

}
function leaksArguments2() {

var args = [].slice.call(arguments);

}
function leaksArguments3() {

var a = arguments;
return function() {
    return a;
};

}
arguments 對(duì)象不能被傳遞或者泄露到任何地方.

處理方法則是使用內(nèi)聯(lián)的代碼創(chuàng)建數(shù)組:

function doesntLeakArguments() {

                // .length 只是一個(gè)整數(shù), 它不會(huì)泄露
                // arguments 對(duì)象本身
var args = new Array(arguments.length);
for(var i = 0; i < args.length; ++i) {
            // i 始終是 arguments 對(duì)象的有效索引
    args[i] = arguments[i];
}
return args;

}
寫一堆代碼很讓人惱火, 所以分析是否值得這么做是值得的. 接下來更多的優(yōu)化總是會(huì)帶來更多的代碼, 而更多的代碼又意味著語(yǔ)義上更顯而易見的退化.

然而如果你有一個(gè) build 的過程, 這其實(shí)可以被一個(gè)不必要求 source map 的宏來實(shí)現(xiàn), 同時(shí)保證源代碼是有效的 JavaScript 代碼.

function doesntLeakArguments() {

INLINE_SLICE(args, arguments);
return args;

}
上面的技巧就用到了 Bluebird 中, 在 build 后會(huì)被擴(kuò)充為下面這樣:

function doesntLeakArguments() {

var $_len = arguments.length;var args = new Array($_len); for(var $_i = 0; $_i < $_len; ++$_i) {args[$_i] = arguments[$_i];}
return args;

}
3.3. 對(duì) arguments 賦值

在非嚴(yán)格模式下, 這其實(shí)是可能的:

function assignToArguments() {

arguments = 3;
return arguments;

}
處理方法: 沒必要寫這么蠢的代碼. 說來在嚴(yán)格模式下, 它也會(huì)直接拋出異常.

怎樣安全地使用 arguments?

僅使用:

arguments.length
arguments[i] 這里 i 必須一直是 arguments 的整數(shù)索引, 并且不能超出邊界
除了 .length 和 [i], 永遠(yuǎn)不要直接使用 arguments (嚴(yán)格地說 x.apply(y, arguments) 是可以的, 但其他的都不行, 比如 .slice. Function#apply 比較特殊)
另外關(guān)于用到 arguments 會(huì)造成 arguments 對(duì)象的分配這一點(diǎn)的 FUD (恐懼), 在使用限于上面提到的安全的方式時(shí)是不必要的.

switch…case

一個(gè) switch…case 語(yǔ)句目前可以有最多 128 個(gè) case 從句, 如果超過了這個(gè)數(shù)量, 包含這個(gè) switch 語(yǔ)句的函數(shù)就無(wú)法被優(yōu)化.

function over128Cases(c) {

switch(c) {
    case 1: break;
    case 2: break;
    case 3: break;
    ...
    case 128: break;
    case 129: break;
}

}
所以請(qǐng)保證 switch 語(yǔ)句的 case 從句不超過 128 個(gè), 可以使用函數(shù)數(shù)組或者 if…else 代替.

for…in

for…in 語(yǔ)句在一些情況下可能導(dǎo)致包含它的函數(shù)無(wú)法被優(yōu)化.

以下解釋了 “for…in 不快” 或者類似的原因.

鍵不是局部變量:

function nonLocalKey1() {

var obj = {}
for(var key in obj);
return function() {
    return key;
};

}
var key;
function nonLocalKey2() {

var obj = {}
for(key in obj);

}
因此鍵既不能是上級(jí)作用于的變量, 也不能被子作用域引用. 它必須是一個(gè)本地變量.

5.2. 被枚舉的對(duì)象不是一個(gè) “簡(jiǎn)單的可枚舉對(duì)象”

5.2.1. 處于 “哈希表模式” 的對(duì)象 (即 “普通化的對(duì)象”, “字典模式” – 以哈希表為數(shù)據(jù)輔助結(jié)構(gòu)的對(duì)象) 不是簡(jiǎn)單的可枚舉對(duì)象.

function hashTableIteration() {

var hashTable = {"-": 3};
for(var key in hashTable);

}
如果你 (在構(gòu)造函數(shù)外) 動(dòng)態(tài)地添加太多屬性到一個(gè)對(duì)象, 刪除屬性, 使用不是合法標(biāo)識(shí)符 (identifier) 的屬性名稱, 這個(gè)對(duì)象就會(huì)變?yōu)楣1砟J? 換言之, 如果你把一個(gè)對(duì)象當(dāng)做哈希表來使用, 它就會(huì)轉(zhuǎn)變?yōu)橐粋€(gè)哈希表. 不要再 for…in 中使用這樣的對(duì)象. 判斷一個(gè)對(duì)象是否為哈希表模式, 可以在開啟 Node.js 的 --allow-natives-syntax 選項(xiàng)時(shí)調(diào)用 console.log(%HasFastProperties(obj)).

5.2.2. 對(duì)象的原型鏈中有可枚舉的屬性

Object.prototype.fn = function() {};
添加上面的代碼會(huì)使所有的對(duì)象 (除了 Object.create(null) 創(chuàng)建的對(duì)象) 的原型鏈中都存在一個(gè)可枚舉的屬性. 由此任何包含 for…in 語(yǔ)句的函數(shù)都無(wú)法得到優(yōu)化 (除非僅枚舉 Object.create(null) 創(chuàng)建的對(duì)象).

你可以通過 Object.defineProperty 來創(chuàng)建不可枚舉的屬性 (不推薦運(yùn)行時(shí)調(diào)用, 但是高效地定義一些靜態(tài)的東西, 比如原型屬性, 還是可以的).

5.2.3. 對(duì)象包含可枚舉的數(shù)組索引

一個(gè)屬性是否是數(shù)組索引是在 ECMAScript 規(guī)范 中定義的.

A property name P (in the form of a String value) is an array index if and only if ToString(ToUint32(P)) is equal to P and ToUint32(P) is not equal to 232?1. A property whose property name is an array index is also called an element
通常來說這些對(duì)象是數(shù)組, 但普通的對(duì)象也可以有數(shù)組索引: normalObj[0] = value;

function iteratesOverArray() {

var arr = [1, 2, 3];
for (var index in arr) {

}

}
所以使用 for…in 遍歷數(shù)組不僅比 for 循環(huán)慢, 還會(huì)導(dǎo)致包含它的整個(gè)函數(shù)無(wú)法被優(yōu)化.

如果傳遞一個(gè)非簡(jiǎn)單的可枚舉對(duì)象到 for…in, 會(huì)導(dǎo)致整個(gè)函數(shù)無(wú)法被優(yōu)化.

處理方法: 總是使用 Object.keys 再使用 for 循環(huán)遍歷數(shù)組. 如果的確需要原型鏈上的所有屬性, 創(chuàng)建一個(gè)多帶帶的輔助函數(shù).

function inheritedKeys(obj) {

var ret = [];
for(var key in obj) {
    ret.push(key);
}
return ret;

}

退出條件較深或者不明確的無(wú)限循環(huán)

寫代碼的時(shí)候, 有時(shí)會(huì)知道自己需要一個(gè)循環(huán), 但不清楚循環(huán)內(nèi)的代碼會(huì)寫成什么樣子. 所以你放了一個(gè) while (true) { 或者 for (;;) {, 之后再在一定條件下中斷循環(huán)接續(xù)之后的代碼, 最后忘了這么一件事. 重構(gòu)的時(shí)間到了, 你發(fā)現(xiàn)這個(gè)函數(shù)很慢, 或者發(fā)現(xiàn)一個(gè)反優(yōu)化的情況 – 可能它就是罪魁.

將循環(huán)的退出條件重構(gòu)到循環(huán)自己的條件部分可能并不容易. 如果代碼的退出條件是結(jié)尾 if 語(yǔ)句的一部分, 并且代碼至少會(huì)執(zhí)行一次, 那可以重構(gòu)為 do { } while (); 循環(huán). 如果退出條件在循環(huán)開頭, 把它放進(jìn)循環(huán)本身的條件部分. 如果退出條件在中間, 你可以嘗試 “滾動(dòng)” 代碼: 每每從開頭移動(dòng)一部分代碼到末尾, 也復(fù)制一份到循環(huán)開始之前. 一旦退出條件可以放置在循環(huán)的條件部分, 或者至少是一個(gè)比較淺的邏輯判斷, 這個(gè)循環(huán)應(yīng)該就不會(huì)被反優(yōu)化了.

原文 it “bails out”.
** 原文 maintenance could easily forget to leave the re-assignent there though.

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/86007.html

相關(guān)文章

  • [] 唯快不破:Web 應(yīng)用的 13 個(gè)優(yōu)化步驟

    摘要:譯文地址譯唯快不破應(yīng)用的個(gè)優(yōu)化步驟前端的逆襲知乎專欄原文地址時(shí)過境遷,應(yīng)用比以往任何時(shí)候都更具交互性。使用負(fù)載均衡方案我們?cè)谥坝懻摼彺娴臅r(shí)候簡(jiǎn)要提到了內(nèi)容分發(fā)網(wǎng)絡(luò)。換句話說,元素的串形訪問會(huì)削弱負(fù)載均衡器以最佳形式 歡迎關(guān)注知乎專欄 —— 前端的逆襲歡迎關(guān)注我的博客,知乎,GitHub。 譯文地址:【譯】唯快不破:Web 應(yīng)用的 13 個(gè)優(yōu)化步驟 - 前端的逆襲 - 知乎專欄原文地...

    haobowd 評(píng)論0 收藏0
  • 2017-07-04 前端日?qǐng)?bào)

    摘要:前端日?qǐng)?bào)精選一起探索的眾成翻譯性能優(yōu)化殺手掘金入門知乎專欄用實(shí)現(xiàn)無(wú)限循環(huán)的無(wú)縫滾動(dòng)蚊子的博客前端每周清單組件解耦之道基于的自動(dòng)化測(cè)試是否為時(shí)已晚中文譯如何在無(wú)損的情況下讓圖片變的更小掘金第期用上古思想寫現(xiàn)代前端踩坑集錦掘金 2017-07-04 前端日?qǐng)?bào) 精選 一起探索 ES6 的 Generators - 眾成翻譯V8 性能優(yōu)化殺手 - 掘金入門TypeScript React - ...

    kelvinlee 評(píng)論0 收藏0
  • 】WebAssembly 初嘗

    摘要:在當(dāng)前階段,僅僅只是字節(jié)碼規(guī)范。如果都沒有將代碼編譯為字節(jié)碼的工具,要起步就很困難了。接下來要做的是使用將格式的代碼轉(zhuǎn)換為二進(jìn)制碼。運(yùn)行文件,最后就能得到瀏覽器需要的真正的二進(jìn)制碼。 本文轉(zhuǎn)載自:眾成翻譯譯者:文藺鏈接:http://www.zcfy.cc/article/1031原文:http://cultureofdevelopment.com/blog/build-your-fi...

    anonymoussf 評(píng)論0 收藏0
  • ( & 轉(zhuǎn)載) 2016 JavaScript 后起之秀

    摘要:在年成為最大贏家,贏得了實(shí)現(xiàn)的風(fēng)暴之戰(zhàn)。和他的競(jìng)爭(zhēng)者位列第二沒有前端開發(fā)者可以忽視和它的生態(tài)系統(tǒng)。他的殺手級(jí)特性是探測(cè)功能,通過檢查任何用戶的功能,以直觀的方式讓開發(fā)人員檢查所有端點(diǎn)。 2016 JavaScript 后起之秀 本文轉(zhuǎn)載自:眾成翻譯譯者:zxhycxq鏈接:http://www.zcfy.cc/article/2410原文:https://risingstars2016...

    darry 評(píng)論0 收藏0
  • JavaScript 究竟是如何工作的?(第一部分)

    摘要:文章的第二部分涵蓋了內(nèi)存管理的概念,不久后將發(fā)布。的標(biāo)準(zhǔn)化工作是由國(guó)際組織負(fù)責(zé)的,相關(guān)規(guī)范被稱為或者。隨著分析器和編譯器不斷地更改字節(jié)碼,的執(zhí)行性能逐漸提高。 原文地址:How Does JavaScript Really Work? (Part 1) 原文作者:Priyesh Patel 譯者:Chor showImg(https://segmentfault.com/img...

    Youngdze 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<