2011-11-25 51 views
12

是否可以重寫下面的JavaScript遞歸函數以使其更快?使用迭代風格在JavaScript中克隆對象

function clone_recursive(object) { 
    var result = {}; 
    for (var key in object) { 
     var value = object[key]; 
     if (typeof value === 'object') { 
      result[key] = clone_recursive(value); 
     } else { 
      result[key] = value; 
     } 
    } 
    return result; 
} 

我重寫了它在迭代風格,但它並沒有獲得任何性能,實際上速度≈20%下降。

function clone_iterative(object) { 
    var result = {}; 
    var queue = [{base: result, value: object}]; 
    var item; 
    while (item = queue.shift()) { 
     var current = item.value; 
     var base = item.base; 
     for (var key in current) { 
      var value = current[key]; 
      if (typeof value === 'object') { 
       var resultValue = base[key] = {}; 
       queue.push({base: resultValue, value: value}); 
      } else { 
       base[key] = value; 
      } 
     } 
    } 
    return result; 
} 

http://jsperf.com/clone-an-object/13

+0

那麼你可以重寫一個遞歸算法使用迭代算法,這有時是必要的,如果遞歸會太深,但你有一個理由要移動到延續專門傳遞?我認爲現有的遞歸算法會更容易遵循... – nnnnnn

+0

我希望看到一個迭代版本。 – NVI

+0

我改變了問題。唯一的目標是加快速度。 – NVI

回答

9

這是值得懷疑的是一個迭代版本將真正成爲速度更快,因爲您要更換多次調用排隊功能的遞歸調用。迭代轉換有助於防止堆棧溢出(因爲調用堆棧往往小於解釋型語言中的堆棧),並且在沒有尾部調用優化的語言中使用尾部遞歸。

+0

在FF3.6上,我回答的版本在經驗上比遞歸版本更快。使用隊列中的對象來保存2個值是主要瓶頸。 –

+0

@LastCoder:在jsFiddle上使用您的基準測試腳本,每次運行10次,每次運行10次,每次運行調用100次函數,結束時的遞歸平均執行時間爲19.331 ms(std dev:0.2424)版本和23.49毫秒(標準開發:0.1749)的FF 3.6下的迭代。在Chrome 15下,遞歸版本的平均時間爲6.268 ms(std dev 0.2291),迭代時間爲6.409 ms(std dev 0.2771)。從我的研究中,遞歸函數在經驗上比迭代更快。如果-1是你,我現在就把它拿回來:-)。 – outis

+0

「如果-1是你」,那不是......我測試了對測試對象的一些修改,看看是否有更多嵌入在每個圖層中的遞歸對象有所不同。無論測試對象被複制的方差如何,遞歸確實會更快5%到15%。我的最初答覆是觀察偏見,我在虛擬機中運行FF的前三個測試代碼運行。 –

2

我嘗試使用隊列的鏈表實現來查看會發生什麼。我覺得你的問題可能已經在函數調用的開銷和shift()不necessaroly是O(1)

Jsperf說,這是最快的這種方式(我在FF7測試):http://jsperf.com/clone-an-object/4 後來我不是當然,如果我沒有搞錯基準,我不太習慣jsperf網站。

編輯︰我在我的代碼中有一個延遲的錯誤。它實際上只是淺拷貝

以下是我使用的代碼的固定版本。它的速度更快那麼其他必要的版本,但還是輸給了遞歸代碼:

function clone_iterative_linked_list(object) { 
    var result = {}; 
    var queueHead = {base: result, value: object, next: null}; 
    var queueTail = queueHead; 

    for(;queueHead; queueHead = queueHead.next){ 
     var item = queueHead; 

     var current = item.value; 
     var base = item.base; 
     for (var key in current) { 
      var value = current[key]; 
      if (typeof value === 'object') { 
       var resultValue = base[key] = {}; 
       queueTail.next = {base: resultValue, value: value, next:null}; 
       queueTail = queueTail.next; 
      } else { 
       base[key] = value; 
      } 
     } 
    } 
    return result; 
} 
+0

http://jsperf.com/clone-an-object/3帶鏈表的迭代版本仍然比遞歸版本慢。雖然它比Safari中的數組快,但在Firefox中速度較慢,而在Chrome中則完全相同。 – NVI

+0

您的版本不會執行深層複製:clone_iterative_linked_list({a:{b:1}})// {a:{}} – NVI

+0

Oh shit,'quehead.next'發生在我有機會將項目添加到名單。現在我想知道在發佈這個測試用例之前我是如何運行的:D – hugomg

3

你(使用隊列)存儲在迭代版本的方法是什麼導致增長放緩。 使用數組堆棧併爲每個項目設置一個條目,而不是包含這兩個項目的對象(基本值爲&值)。

function clone_iterative(object) { 
    var result = {}; 
    var stack = [result, object]; 
    var curr, base; 
    while (curr = stack.pop()) { 
     var base = stack.pop(); 
     for (var key in curr) { 
      var value = curr[key]; 
      if (typeof value === 'object') { 
       stack.push(base[key] = {}); 
       stack.push(value) 
      } else { 
       base[key] = value; 
      } 
     } 
    } 
    return result; 
} 

查看關於JS Fiddle的clone function benchmark suite。在一些運行中,迭代版本比遞歸更快,其他時候遞歸勝出。

+0

這是對原始迭代版本的改進。它與Chrome中的clone_recursive配對運行,但在所有其他瀏覽器中仍然較慢。 http://jsperf.com/clone-an-object/5 – NVI

1

那麼,這個試圖用JSON替代品來只有一個JSON遍歷,也不能更快(見http://jsperf.com/clone-an-object/6):

function clone(x) { 
var r = {}, lastSrc, lastDst = r, stack = [], v; 
function replacer(key, value) { 
    while (this !== lastSrc && stack.length) { 
     lastDst = stack.pop(); 
     lastSrc = stack.pop(); 
    } 
    if (typeof value === "object") { 
     stack.push(lastSrc, lastDst); 
     lastDst[key] = v = new value.constructor; 
     lastDst = v; 
     lastSrc = value; 
     return value; 
    } else { 
     lastDst[key] = value; 
     return undefined; 
    } 
} 
JSON.stringify(x, replacer); 
return r[""]; 
} 
0

迭代。 2個陣列,不要使用pop()

function clone_iterative2(object) { 
    var result = {}; 
    var bases = [result]; 
    var objects = [object]; 
    for (var i = 0, length = 1; i < length; ++i) { 
     var current = objects[i]; 
     var base = bases[i]; 
     for (var key in current) { 
      var value = current[key]; 
      if (typeof value === 'object') { 
       bases.push(base[key] = {}); 
       objects.push(value); 
       ++length; 
      } else { 
       base[key] = value; 
      } 
     } 
    } 
    return result; 
} 

這是迄今爲止最快的迭代。在Chrome Canary(17.0.959.0)中,它是整體速度最快的。在所有其他瀏覽器中,仍然比遞歸慢。

http://jsperf.com/clone-an-object/13