国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

ES6 的解構賦值前每次都創建一個對象嗎?會加重 GC 的負擔嗎?

hightopo / 1812人閱讀

摘要:運行其中的可以查看引擎生成的字節碼。當我們使用解構賦值后我們可以看到,代碼明顯增加了很多,創建了一個對象。擴展閱讀理解的字節碼譯使用參數查看內存由于這個內存占用很小,因此我們加一個循環。

本文來源于知乎上的一個提問。

為了程序的易讀性,我們會使用 ES6 的解構賦值:

function f({a,b}){}
f({a:1,b:2});

這個例子的函數調用中,會真的產生一個對象嗎?如果會,那大量的函數調用會白白生成很多有待 GC 釋放的臨時對象,那么就意味著在函數參數少時,還是需要盡量避免采用解構傳參,而使用傳統的:

function f(a,b){}
f(1,2);

上面的描述其實同時提了好幾個問題:

會不會產生一個對象?

參數少時,是否需要盡量避免采用解構傳參?

對性能(CPU/內存)的影響多大?

1. 從 V8 字節碼分析兩者的性能表現

首先從上面給的代碼例子中,確實會產生一個對象。但是在實際項目中,有很大的概率是不需要產生這個臨時對象的。

我之前寫過一篇文章 使用 D8 分析 javascript 如何被 V8 引擎優化的。那么我們就分析一下你的示例代碼。

function f(a,b){
 return a+b;
}

const d = f(1, 2);

鑒于很多人沒有 d8,因此我們使用 node.js 代替。運行:

node --print-bytecode add.js

其中的 --print-bytecode 可以查看 V8 引擎生成的字節碼。在輸出結果中查找 [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 將當前在累加器中的值存儲在寄存器 r0 中。

LdaSmi [1] 將小整數(Smi)1 加載到累加器寄存器中。

而函數體只有兩行代碼:Ldar a1 和 Add a0, [0]

當我們使用解構賦值后:

[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 創建了一個對象。本來只有 2 條核心指令的函數突然增加到了近 20 條。其中不乏有 JumpIfUndefined、CallRuntimeThrow 這種指令。

擴展閱讀:理解 V8 的字節碼「譯」

2. 使用 --trace-gc 參數查看內存

由于這個內存占用很小,因此我們加一個循環。

function f(a, b){
 return a + b;
}

for (let i = 0; i < 1e8; i++) {
 const d = f(1, 2);
}

console.log(%GetHeapUsage());

%GetHeapUsage() 函數有些特殊,以百分號(%)開頭,這個是 V8 引擎內部調試使用的函數,我們可以通過命令行參數 --allow-natives-syntax 來使用這些函數。

node --trace-gc --allow-natives-syntax add.js

得到結果(為了便于閱讀,我調整了輸出格式):

[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

當使用解構賦值后:

[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

可以看到多了因此內存分配,而且堆空間的使用也比之前多了。使用 --trace_gc_verbose 參數可以查看 gc 更詳細的信息,還可以看到這些內存都是新生代,清理起來的開銷還是比較小的。

3. Escape Analysis 逃逸分析

通過逃逸分析,V8 引擎可以把臨時對象去除。

還考慮之前的函數:

function add({a, b}){
   return a + b;
}

如果我們還有一個函數,double,用于給一個數字加倍。

function double(x) {
   return add({a:x, b:x});
}

而這個 double 函數最終會被編譯為

function double(x){
    return x + x;
}

在 V8 引擎內部,會按照如下步驟進行逃逸分析處理:

首先,增加中間變量:

function add(o){
 return o.a + o.b;
}

function double(x) {
   let o = {a:x, b:x};
   return add(o);
}

把對函數 add 的調用進行內聯展開,變成:

function double(x) {
   let o = {a:x, b:x};
   return o.a + o.b;
}

替換對字段的訪問操作:

function double(x) {
   let o = {a:x, b:x};
   return x + x;
}

刪除沒有使用到的內存分配:

function double(x) {
   return x + x;
}

通過 V8 的逃逸分析,把本來分配到堆上的對象去除了。

4. 結論

不要做這種語法層面的微優化,引擎會去優化的,業務代碼還是更加關注可讀性和可維護性。如果你寫的是庫代碼,可以嘗試這種優化,把參數展開后直接傳遞,到底能帶來多少性能收益還得看最終的基準測試。

舉個例子就是 Chrome 49 開始支持 Proxy,直到一年之后的 Chrome 62 才改進了 Proxy 的性能,使 Proxy 的整體性能提升了 24% ~ 546%。

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/95767.html

相關文章

  • ES6學習(二)之解構賦值及其原理

    摘要:基本原理解構是提供的語法糖,其實內在是針對可迭代對象的接口,通過遍歷器按順序獲取對應的值進行賦值。屬性值返回一個對象的無參函數,被返回對象符合迭代器協議。迭代器協議定義了標準的方式來產生一個有限或無限序列值。 更多系列文章請看 1、基本語法 1.1、數組 // 基礎類型解構 let [a, b, c] = [1, 2, 3] console.log(a, b, c) // 1, 2, ...

    chunquedong 評論0 收藏0
  • 翻譯連載 |《你不知道JS》姊妹篇 |《JavaScript 輕量級函數式編程》- 第 2 章:函

    摘要:從某些方面來講,這章回顧的函數知識并不是針對函數式編程者,非函數式編程者同樣需要了解。什么是函數針對函數式編程,很自然而然的我會想到從函數開始。如果你計劃使用函數式編程,你應該盡可能多地使用函數,而不是程序。指的是一個函數聲明的形參數量。 原文地址:Functional-Light-JS 原文作者:Kyle Simpson - 《You-Dont-Know-JS》作者 關于譯者:...

    Riddler 評論0 收藏0
  • ES6解構賦值運算符

    摘要:本次我領到的任務是在中有一個解構賦值運算符,可以大大方便數據字段的獲取。解構賦值運算符配合會比較有用。 本次我領到的任務是: 在ES6中有一個解構賦值運算符,可以大大方便數據字段的獲取。 比如 const [a, b] = [1, 2, 3]; const {name, age} = {name: helijia, age: 3}; 上面的語句是我們常用的,可是你能解釋為什么下面的...

    qpal 評論0 收藏0
  • ES6 解構賦值

    摘要:解構賦值允許按照一定模式,從數組和對象中提取值,對變量進行賦值,這被稱為解構。由于和無法轉為對象,所以對它們進行解構賦值,都會報錯。 解構賦值 ES6 允許按照一定模式,從數組和對象中提取值,對變量進行賦值,這被稱為解構(Destructuring)。查看阮老師的原文 解構賦值的要點:前后結構一致 模式匹配,等號兩邊的模式相同,按照對應關系(位置或名稱),左邊的變量被賦予對應的值。 ...

    mo0n1andin 評論0 收藏0

發表評論

0條評論

hightopo

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<