2011-11-25 5 views
12

È possibile riscrivere la seguente funzione ricorsiva JavaScript per renderla più veloce?Uso dello stile iterativo per clonare un oggetto in 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; 
} 

ho riscritto in uno stile iterativo, ma non guadagna qualsiasi prestazione, infatti la velocità è sceso da ≈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

Ebbene si può riscrivere un algoritmo ricorsivo per utilizzare un algoritmo iterativo, che a volte è necessario se la ricorsione sta andando troppo in profondità, ma non si dispone di un motivo per voler passare a continuazione passando in particolare? Penso che l'algoritmo ricorsivo esistente sarà più facile da seguire ... – nnnnnn

+0

Mi piacerebbe vedere anche una versione iterativa. – NVI

+0

Ho cambiato la domanda.L'unico obiettivo è renderlo più veloce. – NVI

risposta

9

E 'doubtable che una versione iterativa sarebbe davvero essere più veloce, come si sta sostituendo una chiamata ricorsiva con più chiamate a funzioni in coda. Nei casi in cui una trasformazione per l'iterazione aiuta a prevenire gli overflow dello stack (poiché gli stack di chiamate tendono ad essere più piccoli degli heap nelle lingue interpretate) e con ricorsione in coda nelle lingue senza ottimizzazione di chiamata di coda.

+0

Su FF3.6 la versione con cui ho risposto è stata empiricamente più veloce rispetto alla versione ricorsiva. L'uso di un oggetto nella coda per contenere i 2 valori era il collo di bottiglia principale. –

+0

@LastCoder: utilizzando lo script di riferimento su jsFiddle, eseguendo 10 tentativi di 10 esecuzioni ogni processo, in cui ciascuna funzione viene chiamata 100 volte per esecuzione, si ottiene un tempo di esecuzione medio di 19,331 ms (dev standard: 0,2424) per il ricorsivo versione e 23.49 ms (dev standard: 0.1749) per l'iterativo sotto FF 3.6. Sotto Chrome 15, il tempo medio per la versione ricorsiva è 6.268 ms (std dev 0.2291) e l'iterativo è 6.409 ms (std dev 0.2771). Dal mio studio, la funzione ricorsiva è empiricamente più veloce di quella iterativa. Se tu fossi il -1, lo riprenderò ora :-). – outis

+0

"Se il -1 eri tu", non era ... Eseguo alcune modifiche all'oggetto test vedendo se avere più oggetti ricorsivi incorporati in ogni livello ha fatto la differenza. La ricorsione è infatti più veloce dal 5% al ​​15%, indipendentemente dalla varianza dell'oggetto di prova da copiare. La mia risposta iniziale è stata l'osservazione delle mie prime tre esecuzioni del codice di test che eseguiva FF in una macchina virtuale. –

2

Ho provato a utilizzare un'implementazione di lista collegata della coda per vedere cosa succede. Credo che i vostri problemi avrebbero potuto essere la funzione di chiamata in testa e lo spostamento() non necessaroly essendo O (1)

Jsperf ha detto che era il più veloce in questo modo (ho testato su FF7): http://jsperf.com/clone-an-object/4 ma poi io non sono certo se non ho rovinato il benchmark in quanto non sono molto abituato al sito web di jsperf.

Modifica: Ho avuto un errore ritardato nel mio codice. In realtà era solo una copia superficiale

Quanto segue è la versione fissa del codice che ho usato. La sua più velocemente allora l'altra versione imperativo, ma perde ancora il codice ricorsivo:

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 la versione iterativa con una lista collegata è ancora più lenta di quella ricorsiva. Anche se è più veloce di un array in Safari, più lento in Firefox e esattamente lo stesso in Chrome. – NVI

+0

La tua versione non esegue la copia profonda: clone_iterative_linked_list ({a: {b: 1}}) // {a: {}} – NVI

+0

Oh shit, 'quehead.next' accade prima che io abbia la possibilità di aggiungere elementi al elenco. Ora mi chiedo come diavolo il test case che ho fatto prima di pubblicare questo ha funzionato anche: D – hugomg

3

Il modo in cui si memorizzano (con la coda) nella versione iterativa è ciò che sta causando il rallentamento. Utilizzare uno stack di array e disporre di una voce per ciascun elemento anziché di un oggetto che contiene entrambi gli elementi (valore base &).

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; 
} 

Scopri i clone function benchmark suite su JS Fiddle. In alcune corse la versione iterativa è più veloce di ricorsiva, altre volte la ricorsione vince.

+0

È un miglioramento rispetto alla versione originale iterativa. Funziona in coppia con clone_recursive in Chrome, ma è ancora più lento in tutti gli altri browser. http://jsperf.com/clone-an-object/5 – NVI

1

Bene, questo ha cercato di utilizzare JSON sostituto per avere un solo JSON attraversamento, ma anche non più veloce (vedi 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

iterativo. 2 array, non usano 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; 
} 

E 'il più veloce iterativo finora. In Chrome Canary (17.0.959.0) è il più veloce in assoluto. Ancora più lento del ricorsivo in tutti gli altri browser.

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