摘要:運(yùn)行其中的可以查看引擎生成的字節(jié)碼。當(dāng)我們使用解構(gòu)賦值后我們可以看到,代碼明顯增加了很多,創(chuàng)建了一個(gè)對(duì)象。擴(kuò)展閱讀理解的字節(jié)碼譯使用參數(shù)查看內(nèi)存由于這個(gè)內(nèi)存占用很小,因此我們加一個(gè)循環(huán)。
本文來源于知乎上的一個(gè)提問。
為了程序的易讀性,我們會(huì)使用 ES6 的解構(gòu)賦值:
function f({a,b}){} f({a:1,b:2});
這個(gè)例子的函數(shù)調(diào)用中,會(huì)真的產(chǎn)生一個(gè)對(duì)象嗎?如果會(huì),那大量的函數(shù)調(diào)用會(huì)白白生成很多有待 GC 釋放的臨時(shí)對(duì)象,那么就意味著在函數(shù)參數(shù)少時(shí),還是需要盡量避免采用解構(gòu)傳參,而使用傳統(tǒng)的:
function f(a,b){} f(1,2);
上面的描述其實(shí)同時(shí)提了好幾個(gè)問題:
會(huì)不會(huì)產(chǎn)生一個(gè)對(duì)象?
參數(shù)少時(shí),是否需要盡量避免采用解構(gòu)傳參?
對(duì)性能(CPU/內(nèi)存)的影響多大?
1. 從 V8 字節(jié)碼分析兩者的性能表現(xiàn)首先從上面給的代碼例子中,確實(shí)會(huì)產(chǎn)生一個(gè)對(duì)象。但是在實(shí)際項(xiàng)目中,有很大的概率是不需要產(chǎn)生這個(gè)臨時(shí)對(duì)象的。
我之前寫過一篇文章 使用 D8 分析 javascript 如何被 V8 引擎優(yōu)化的。那么我們就分析一下你的示例代碼。
function f(a,b){ return a+b; } const d = f(1, 2);
鑒于很多人沒有 d8,因此我們使用 node.js 代替。運(yùn)行:
node --print-bytecode add.js
其中的 --print-bytecode 可以查看 V8 引擎生成的字節(jié)碼。在輸出結(jié)果中查找 [generating bytecode for function: f]:
[generating bytecode for function: ] Parameter count 6 Frame size 32 0000003AC126862A @ 0 : 6e 00 00 02 CreateClosure [0], [0], #2 0000003AC126862E @ 4 : 1e fb Star r0 10 E> 0000003AC1268630 @ 6 : 91 StackCheck 98 S> 0000003AC1268631 @ 7 : 03 01 LdaSmi [1] 0000003AC1268633 @ 9 : 1e f9 Star r2 0000003AC1268635 @ 11 : 03 02 LdaSmi [2] 0000003AC1268637 @ 13 : 1e f8 Star r3 98 E> 0000003AC1268639 @ 15 : 51 fb f9 f8 01 CallUndefinedReceiver2 r0, r2, r3, [1] 0000003AC126863E @ 20 : 04 LdaUndefined 107 S> 0000003AC126863F @ 21 : 95 Return Constant pool (size = 1) Handler Table (size = 16) [generating bytecode for function: f] Parameter count 3 Frame size 0 72 E> 0000003AC1268A6A @ 0 : 91 StackCheck 83 S> 0000003AC1268A6B @ 1 : 1d 02 Ldar a1 91 E> 0000003AC1268A6D @ 3 : 2b 03 00 Add a0, [0] 94 S> 0000003AC1268A70 @ 6 : 95 Return Constant pool (size = 0) Handler Table (size = 16)
Star r0 將當(dāng)前在累加器中的值存儲(chǔ)在寄存器 r0 中。
LdaSmi [1] 將小整數(shù)(Smi)1 加載到累加器寄存器中。
而函數(shù)體只有兩行代碼:Ldar a1 和 Add a0, [0]。
當(dāng)我們使用解構(gòu)賦值后:
[generating bytecode for function: ] Parameter count 6 Frame size 24 000000D24A568662 @ 0 : 6e 00 00 02 CreateClosure [0], [0], #2 000000D24A568666 @ 4 : 1e fb Star r0 10 E> 000000D24A568668 @ 6 : 91 StackCheck 100 S> 000000D24A568669 @ 7 : 6c 01 03 29 f9 CreateObjectLiteral [1], [3], #41, r2 100 E> 000000D24A56866E @ 12 : 50 fb f9 01 CallUndefinedReceiver1 r0, r2, [1] 000000D24A568672 @ 16 : 04 LdaUndefined 115 S> 000000D24A568673 @ 17 : 95 Return Constant pool (size = 2) Handler Table (size = 16) [generating bytecode for function: f] Parameter count 2 Frame size 40 72 E> 000000D24A568AEA @ 0 : 91 StackCheck 000000D24A568AEB @ 1 : 1f 02 fb Mov a0, r0 000000D24A568AEE @ 4 : 1d fb Ldar r0 000000D24A568AF0 @ 6 : 89 06 JumpIfUndefined [6] (000000D24A568AF6 @ 12) 000000D24A568AF2 @ 8 : 1d fb Ldar r0 000000D24A568AF4 @ 10 : 88 10 JumpIfNotNull [16] (000000D24A568B04 @ 26) 000000D24A568AF6 @ 12 : 03 3f LdaSmi [63] 000000D24A568AF8 @ 14 : 1e f8 Star r3 000000D24A568AFA @ 16 : 09 00 LdaConstant [0] 000000D24A568AFC @ 18 : 1e f7 Star r4 000000D24A568AFE @ 20 : 53 e8 00 f8 02 CallRuntime [NewTypeError], r3-r4 74 E> 000000D24A568B03 @ 25 : 93 Throw 74 S> 000000D24A568B04 @ 26 : 20 fb 00 02 LdaNamedProperty r0, [0], [2] 000000D24A568B08 @ 30 : 1e fa Star r1 76 S> 000000D24A568B0A @ 32 : 20 fb 01 04 LdaNamedProperty r0, [1], [4] 000000D24A568B0E @ 36 : 1e f9 Star r2 85 S> 000000D24A568B10 @ 38 : 1d f9 Ldar r2 93 E> 000000D24A568B12 @ 40 : 2b fa 06 Add r1, [6] 96 S> 000000D24A568B15 @ 43 : 95 Return Constant pool (size = 2) Handler Table (size = 16)
我們可以看到,代碼明顯增加了很多,CreateObjectLiteral 創(chuàng)建了一個(gè)對(duì)象。本來只有 2 條核心指令的函數(shù)突然增加到了近 20 條。其中不乏有 JumpIfUndefined、CallRuntime 、Throw 這種指令。
擴(kuò)展閱讀:理解 V8 的字節(jié)碼「譯」
2. 使用 --trace-gc 參數(shù)查看內(nèi)存由于這個(gè)內(nèi)存占用很小,因此我們加一個(gè)循環(huán)。
function f(a, b){ return a + b; } for (let i = 0; i < 1e8; i++) { const d = f(1, 2); } console.log(%GetHeapUsage());
%GetHeapUsage() 函數(shù)有些特殊,以百分號(hào)(%)開頭,這個(gè)是 V8 引擎內(nèi)部調(diào)試使用的函數(shù),我們可以通過命令行參數(shù) --allow-natives-syntax 來使用這些函數(shù)。
node --trace-gc --allow-natives-syntax add.js
得到結(jié)果(為了便于閱讀,我調(diào)整了輸出格式):
[10192:0000000000427F50] 26 ms: Scavenge 3.4 (6.3) -> 3.1 (7.3) MB, 1.3 / 0.0 ms allocation failure [10192:0000000000427F50] 34 ms: Scavenge 3.6 (7.3) -> 3.5 (8.3) MB, 0.8 / 0.0 ms allocation failure 4424128
當(dāng)使用解構(gòu)賦值后:
[7812:00000000004513E0] 27 ms: Scavenge 3.4 (6.3) -> 3.1 (7.3) MB, 1.0 / 0.0 ms allocation failure [7812:00000000004513E0] 36 ms: Scavenge 3.6 (7.3) -> 3.5 (8.3) MB, 0.7 / 0.0 ms allocation failure [7812:00000000004513E0] 56 ms: Scavenge 4.6 (8.3) -> 4.1 (11.3) MB, 0.5 / 0.0 ms allocation failure 4989872
可以看到多了因此內(nèi)存分配,而且堆空間的使用也比之前多了。使用 --trace_gc_verbose 參數(shù)可以查看 gc 更詳細(xì)的信息,還可以看到這些內(nèi)存都是新生代,清理起來的開銷還是比較小的。
3. Escape Analysis 逃逸分析通過逃逸分析,V8 引擎可以把臨時(shí)對(duì)象去除。
還考慮之前的函數(shù):
function add({a, b}){ return a + b; }
如果我們還有一個(gè)函數(shù),double,用于給一個(gè)數(shù)字加倍。
function double(x) { return add({a:x, b:x}); }
而這個(gè) double 函數(shù)最終會(huì)被編譯為
function double(x){ return x + x; }
在 V8 引擎內(nèi)部,會(huì)按照如下步驟進(jìn)行逃逸分析處理:
首先,增加中間變量:
function add(o){ return o.a + o.b; } function double(x) { let o = {a:x, b:x}; return add(o); }
把對(duì)函數(shù) add 的調(diào)用進(jìn)行內(nèi)聯(lián)展開,變成:
function double(x) { let o = {a:x, b:x}; return o.a + o.b; }
替換對(duì)字段的訪問操作:
function double(x) { let o = {a:x, b:x}; return x + x; }
刪除沒有使用到的內(nèi)存分配:
function double(x) { return x + x; }
通過 V8 的逃逸分析,把本來分配到堆上的對(duì)象去除了。
4. 結(jié)論不要做這種語(yǔ)法層面的微優(yōu)化,引擎會(huì)去優(yōu)化的,業(yè)務(wù)代碼還是更加關(guān)注可讀性和可維護(hù)性。如果你寫的是庫(kù)代碼,可以嘗試這種優(yōu)化,把參數(shù)展開后直接傳遞,到底能帶來多少性能收益還得看最終的基準(zhǔn)測(cè)試。
舉個(gè)例子就是 Chrome 49 開始支持 Proxy,直到一年之后的 Chrome 62 才改進(jìn)了 Proxy 的性能,使 Proxy 的整體性能提升了 24% ~ 546%。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/95767.html
摘要:基本原理解構(gòu)是提供的語(yǔ)法糖,其實(shí)內(nèi)在是針對(duì)可迭代對(duì)象的接口,通過遍歷器按順序獲取對(duì)應(yīng)的值進(jìn)行賦值。屬性值返回一個(gè)對(duì)象的無(wú)參函數(shù),被返回對(duì)象符合迭代器協(xié)議。迭代器協(xié)議定義了標(biāo)準(zhǔn)的方式來產(chǎn)生一個(gè)有限或無(wú)限序列值。 更多系列文章請(qǐng)看 1、基本語(yǔ)法 1.1、數(shù)組 // 基礎(chǔ)類型解構(gòu) let [a, b, c] = [1, 2, 3] console.log(a, b, c) // 1, 2, ...
摘要:從某些方面來講,這章回顧的函數(shù)知識(shí)并不是針對(duì)函數(shù)式編程者,非函數(shù)式編程者同樣需要了解。什么是函數(shù)針對(duì)函數(shù)式編程,很自然而然的我會(huì)想到從函數(shù)開始。如果你計(jì)劃使用函數(shù)式編程,你應(yīng)該盡可能多地使用函數(shù),而不是程序。指的是一個(gè)函數(shù)聲明的形參數(shù)量。 原文地址:Functional-Light-JS 原文作者:Kyle Simpson?。 禮ou-Dont-Know-JS》作者 關(guān)于譯者:...
摘要:本次我領(lǐng)到的任務(wù)是在中有一個(gè)解構(gòu)賦值運(yùn)算符,可以大大方便數(shù)據(jù)字段的獲取。解構(gòu)賦值運(yùn)算符配合會(huì)比較有用。 本次我領(lǐng)到的任務(wù)是: 在ES6中有一個(gè)解構(gòu)賦值運(yùn)算符,可以大大方便數(shù)據(jù)字段的獲取。 比如 const [a, b] = [1, 2, 3]; const {name, age} = {name: helijia, age: 3}; 上面的語(yǔ)句是我們常用的,可是你能解釋為什么下面的...
摘要:解構(gòu)賦值允許按照一定模式,從數(shù)組和對(duì)象中提取值,對(duì)變量進(jìn)行賦值,這被稱為解構(gòu)。由于和無(wú)法轉(zhuǎn)為對(duì)象,所以對(duì)它們進(jìn)行解構(gòu)賦值,都會(huì)報(bào)錯(cuò)。 解構(gòu)賦值 ES6 允許按照一定模式,從數(shù)組和對(duì)象中提取值,對(duì)變量進(jìn)行賦值,這被稱為解構(gòu)(Destructuring)。查看阮老師的原文 解構(gòu)賦值的要點(diǎn):前后結(jié)構(gòu)一致 模式匹配,等號(hào)兩邊的模式相同,按照對(duì)應(yīng)關(guān)系(位置或名稱),左邊的變量被賦予對(duì)應(yīng)的值。 ...
閱讀 1005·2021-11-15 18:06
閱讀 2371·2021-10-08 10:04
閱讀 2656·2019-08-28 18:03
閱讀 906·2019-08-26 13:42
閱讀 1924·2019-08-26 11:31
閱讀 2431·2019-08-23 17:13
閱讀 932·2019-08-23 16:45
閱讀 2059·2019-08-23 14:11