簡體   English   中英

尾部呼叫優化javascript

[英]tail call optimization javascript

注意:這僅是為了學習和改善自己。 我知道可用的數組排序方法。 我只是想降低TCO的基礎知識。

當前嘗試使用遞歸進行排序算法。 但是,當我嘗試處理大型數據集(+4000個對象)時,仍然出現堆棧溢出錯誤。 我正在嘗試實施TCO。 我對這個概念還很陌生,但是我想知道它的要旨。 但是,我仍然收到堆棧溢出錯誤。

const sort = (arr, counter) => {
  if (!counter) {
    counter = arr.length - 1;
  }
  for (let n = 1; n <= counter; n++) {
    if(arr[n - 1] < arr[n]) {
      let placeHolder = arr[n];
      arr[n] = arr[n - 1];
      arr[n - 1] = placeHolder;
    }
  }
  counter -= 1;
  return counter === 0 ? arr : sort(arr, counter);
};

function sortRecursive(arr) {
  return sort(arr);
}

更新:

我設法使其正常運行,但是我不太明白為什么。 我設法毫無問題地處理了100,000次遞歸。 我必須移動檢查計數器是否已定義的布爾值。 但是,我不太明白為什么它能起作用。

const sort = (arr, counter) => {
  if (!counter) {
    counter = arr.length - 1;
  }
  for (let n = 1; n <= counter; n++) {
    if(arr[n - 1] < arr[n]) {
      let placeHolder = arr[n];
      arr[n] = arr[n - 1];
      arr[n - 1] = placeHolder;
    }
  }
  counter -= 1;
  if (counter === 0) {
    return arr;
  } else {
    return sort(arr, counter);
  }
};

function sortRecursive(arr) {
  return sort(arr, arr.length - 1);
}

輸出:

let firstArr = [];
let secondArr = [];

for (let x = 0; x < 100000; x++) {
  firstArr.push(Math.ceil(Math.random() * 100000));
  secondArr.push(Math.ceil(Math.random() * 100000));
}

sortRecursive(firstArr);
//Array[100000]

您可能知道,尾調用優化是一種編譯器技術,可以通過不為每個遞歸調用分配更多的內存來使程序無限遞歸。

Javascript當前尚未進行尾調用優化,但是語言規范的ES2015標准包括TCO。 每次函數使用Javascript調用自身時,都會創建一個新的堆棧框架,分配新的內存,因此它最終將耗盡並崩潰。

有避免這種情況的技術,包括蹦床和不使用遞歸循環。 但是目前您不能無限遞歸地使用Javascript。

為什么需要遞歸(因為它超過了每個調用堆棧,所以它永遠無法使用4000+個元素)? 你不能做:

const sort = (arr) => {
     var counter = arr.length;
     while(counter-->0){
       for (let n = 1; n <= counter; n++) {
           if(arr[n - 1] < arr[n]) {
              let placeHolder = arr[n];
              arr[n] = arr[n - 1];
              arr[n - 1] = placeHolder;
           }
        }
     }
     return arr;
}

如果您想進行某種遞歸,則可以使用qeue來保持堆棧為空(需要傳遞回調):

setTimeout(sort,0,arr,counter);//instead of sort(arr,counter);

順便說一句,更容易,更快(因為它是本地實現的):

arr.sort((a,b)=>a-b);

確定要進行尾聲優化嗎?

這是使用更新后的代碼進行的測試。 我更改的唯一內容是:

  1. 增加了'use strict'; 將代碼置於嚴格模式。 某些將來支持TCO的瀏覽器可能要求使用嚴格模式才能使用TCO。
  2. 添加了console.trace()以在每個sort()調用上打印調用堆棧。
  3. 出於我上面的評論中提到的原因,將測試數組設置更改為使用Math.floor()而不是Math.ceil()
  4. 將數組長度更改為10。

運行代碼片段之前,請打開開發者控制台,並觀察調用堆棧的蹤跡。

我在最新版本的Chrome 59.0.3071.109,Firefox 54.0和Edge 15.15063中對此進行了測試。 來自所有這些堆棧的堆棧跟蹤顯示了每次調用時調用堆棧的增長,表明沒有尾部調用優化。

只是為了踢球,我還在length = 100000 Chrome中嘗試了一下。 它運行了很長的時間(可能是一分鍾左右),然后在堆棧達到大約10257個調用的深度時,由於堆棧溢出而失敗。 為了進行比較,標准sort( function( a, b ) { return b - a; } )在大約5秒鍾內完成。

這是一篇有關JavaScript尾部調用優化和相關主題的好文章 文章提到的一件事是,您可以通過使用--harmony_tailcalls命令行開關以及'use strict';來在node.js中獲得TCO 'use strict';

 'use strict'; const sort = (arr, counter) => { console.trace(); if (!counter) { counter = arr.length - 1; } for (let n = 1; n <= counter; n++) { if(arr[n - 1] < arr[n]) { let placeHolder = arr[n]; arr[n] = arr[n - 1]; arr[n - 1] = placeHolder; } } counter -= 1; if (counter === 0) { return arr; } else { return sort(arr, counter); } }; function sortRecursive(arr) { return sort(arr, arr.length - 1); } let firstArr = []; let length = 10; for (let x = 0; x < length; x++) { firstArr.push(Math.floor(Math.random() * length)); } console.clear(); sortRecursive(firstArr); //firstArr.sort( function( a, b ) { return b - a } ); console.log( firstArr ); 

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM