Perché la concatenazione di stringhe è più veloce dell'unione di array?


114

Oggi ho letto questo thread sulla velocità di concatenazione delle stringhe.

Sorprendentemente, la concatenazione di stringhe è stata la vincitrice:

http://jsben.ch/#/OJ3vo

Il risultato è stato l'opposto di quello che pensavo. Inoltre, ci sono molti articoli su questo che spiegano in modo opposto in questo modo .

Posso immaginare che i browser siano ottimizzati per stringere concatl'ultima versione, ma come lo fanno? Possiamo dire che è meglio usare +quando si concatenano le stringhe?


Aggiornare

Quindi, nei browser moderni la concatenazione di stringhe è ottimizzata, quindi l'uso dei +segni è più veloce rispetto all'uso joinquando si desidera concatenare le stringhe.

Ma @Arthur ha sottolineato che joinè più veloce se si desidera effettivamente unire le stringhe con un separatore.


Aggiornamento - 2020
Chrome: Array joinè quasi un 2 times fasterconcatenamento di stringhe + Vedi: https://stackoverflow.com/a/54970240/984471

Come nota:

  • Array joinè meglio se lo hailarge strings
  • Se abbiamo bisogno di generare several small stringsnell'output finale, è meglio andare con string concat +, altrimenti andare con Array richiederà diverse conversioni da Array a String alla fine, che è un sovraccarico delle prestazioni.


1
Questo codice dovrebbe produrre 500 terabyte di spazzatura, ma viene eseguito in 200 ms. Penso che assegnino solo un po 'più di spazio per una stringa, e quando aggiungi una breve stringa, di solito si inserisce in uno spazio extra.
Ivan Kuckir

Risposte:


149

Le ottimizzazioni delle stringhe del browser hanno modificato l'immagine di concatenazione delle stringhe.

Firefox è stato il primo browser a ottimizzare la concatenazione di stringhe. A partire dalla versione 1.0, la tecnica dell'array è effettivamente più lenta rispetto all'utilizzo dell'operatore più in tutti i casi. Anche altri browser hanno ottimizzato la concatenazione di stringhe, quindi Safari, Opera, Chrome e Internet Explorer 8 mostrano anche prestazioni migliori utilizzando l'operatore plus. Internet Explorer precedente alla versione 8 non disponeva di tale ottimizzazione, quindi la tecnica dell'array è sempre più veloce dell'operatore plus.

- Scrittura di JavaScript efficiente: Capitolo 7 - Siti web ancora più veloci

Il motore javascript V8 (utilizzato in Google Chrome) utilizza questo codice per eseguire la concatenazione di stringhe:

// ECMA-262, section 15.5.4.6
function StringConcat() {
  if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) {
    throw MakeTypeError("called_on_null_or_undefined", ["String.prototype.concat"]);
  }
  var len = %_ArgumentsLength();
  var this_as_string = TO_STRING_INLINE(this);
  if (len === 1) {
    return this_as_string + %_Arguments(0);
  }
  var parts = new InternalArray(len + 1);
  parts[0] = this_as_string;
  for (var i = 0; i < len; i++) {
    var part = %_Arguments(i);
    parts[i + 1] = TO_STRING_INLINE(part);
  }
  return %StringBuilderConcat(parts, len + 1, "");
}

Quindi, internamente lo ottimizzano creando un InternalArray (la partsvariabile), che viene quindi riempito. La funzione StringBuilderConcat viene chiamata con queste parti. È veloce perché la funzione StringBuilderConcat è un codice C ++ fortemente ottimizzato. È troppo lungo per essere citato qui, ma cerca nel file runtime.cc per RUNTIME_FUNCTION(MaybeObject*, Runtime_StringBuilderConcat)vedere il codice.


4
Hai tralasciato la cosa davvero interessante, l'array viene utilizzato solo per chiamare Runtime_StringBuilderConcat con conteggi di argomenti diversi. Ma il vero lavoro viene svolto lì.
evilpie

41
Ottimizzazione 101: dovresti mirare al meno lento! ad esempio, arr.join vs str+su Chrome ottieni (in operazioni al secondo) 25k/s vs 52k/s. su firefox new ottieni 76k/s vs 212k/s. quindi str+è PIÙ VELOCE. ma guardiamo altri browser. Opera dà 43k / s contro 26k / s. IE dà 1300/s vs 1002/s. guarda cosa succede? l' unico browser che HA BISOGNO di ottimizzazione sarebbe meglio usare ciò che è più lento su tutti gli altri, dove non ha importanza. Quindi, nessuno di questi articoli capisce nulla sulle prestazioni.
gcb

45
@gcb, gli unici browser per i quali il join è più veloce non dovrebbero essere usati. Il 95% dei miei utenti ha FF e Chrome. Ottimizzerò per il caso d'uso del 95%.
Paul Draper

7
@PaulDraper se il 90% degli utenti utilizza un browser veloce e una delle due opzioni che scegli li guadagnerà 0,001 secondi, ma il 10% dei tuoi utenti guadagnerà 2 secondi se scegli di penalizzare gli altri utenti da 0,001 secondi ... la decisione è chiaro. se non riesci a vederlo, mi dispiace per la persona per cui codifichi.
gcb

7
I browser più vecchi alla fine scompariranno, ma le probabilità che qualcuno torni a convertire tutti quei join di array non è probabile. È meglio programmare per il futuro purché non costituisca un grave inconveniente per gli utenti attuali. È probabile che ci siano cose più importanti di cui preoccuparsi delle prestazioni di concatenazione quando si ha a che fare con i vecchi browser.
Thomas Higginbotham

23

Firefox è veloce perché utilizza qualcosa chiamato Ropes ( Ropes: an Alternative to Strings ). Una corda è fondamentalmente solo un DAG, dove ogni nodo è una stringa.

Quindi, ad esempio, se lo facessi a = 'abc'.concat('def'), l'oggetto appena creato sarebbe simile a questo. Ovviamente questo non è esattamente come appare in memoria, perché è ancora necessario avere un campo per il tipo di stringa, la lunghezza e forse altro.

a = {
 nodeA: 'abc',
 nodeB: 'def'
}

E b = a.concat('123')

b = {
  nodeA: a, /* {
             nodeA: 'abc',
             nodeB: 'def'
          } */
  nodeB: '123'
}           

Quindi nel caso più semplice la VM non deve fare quasi nessun lavoro. L'unico problema è che questo rallenta un po 'altre operazioni sulla stringa risultante. Anche questo ovviamente riduce l'overhead della memoria.

D'altra parte di ['abc', 'def'].join('')solito allocava solo la memoria per disporre la nuova stringa in memoria. (Forse questo dovrebbe essere ottimizzato)


6

So che questo è un vecchio thread, ma il tuo test non è corretto. Stai facendo output += myarray[i];mentre dovrebbe essere più come output += "" + myarray[i];perché ti sei dimenticato, che devi incollare oggetti insieme a qualcosa. Il codice concat dovrebbe essere qualcosa del tipo:

var output = myarray[0];
for (var i = 1, len = myarray.length; i<len; i++){
    output += "" + myarray[i];
}

In questo modo, stai facendo due operazioni invece di una a causa dell'incollaggio degli elementi.

Array.join() è più veloce.


Non ho la tua risposta. Qual è la differenza tra il put "" +e l'originale?
Sanghyun Lee

Sono due operazioni invece di una per ogni iterazione che richiede più tempo.
Arthur

1
E perché dobbiamo metterlo? Stiamo già incollando oggetti outputsenza di essa.
Sanghyun Lee

Perché è così che funziona join. Ad esempio, puoi anche fare Array.join(",")ciò che non funzionerà con il tuo forloop
Arthur

Oh ho capito. Hai già testato per vedere se join () è più veloce?
Sanghyun Lee

5

Per grandi quantità di dati il ​​join è più veloce, quindi la domanda è formulata in modo errato.

let result = "";
let startTime = new Date().getTime();
for (let i = 0; i < 2000000; i++) {
    result += "x";
}
console.log("concatenation time: " + (new Date().getTime() - startTime));

startTime = new Date().getTime();
let array = new Array(2000000);
for (let i = 0; i < 2000000; i++) {
    array[i] = "x";
}
result = array.join("");
console.log("join time: " + (new Date().getTime() - startTime));

Testato su Chrome 72.0.3626.119, Firefox 65.0.1, Edge 42.17134.1.0. Nota che è più veloce anche con la creazione dell'array inclusa!


~ Agosto 2020. Vero. In Chrome: Array Join time: 462. String Concat (+) time: 827. Join è quasi 2 volte più veloce.
Manohar Reddy Poreddy

3

I benchmark sono banali. La concatenazione ripetuta degli stessi tre elementi sarà inline, i risultati si dimostreranno deterministici e memoizzati, il garbage handler getterà semplicemente via gli oggetti dell'array (che saranno di dimensioni quasi nulle) e probabilmente solo spinto e saltato fuori dallo stack a causa di no riferimenti esterni e perché le stringhe non cambiano mai. Sarei più impressionato se il test fosse un gran numero di stringhe generate casualmente. Come in uno o due concerti di archi.

Array.join FTW!


2

Direi che con le stringhe è più facile preallocare un buffer più grande. Ogni elemento è solo 2 byte (se UNICODE), quindi anche se sei prudente, puoi preallocare un buffer abbastanza grande per la stringa. Con arraysogni elemento è più "complesso", perché ogni elemento è un Object, quindi un'implementazione conservativa preallocherà spazio per meno elementi.

Se provi ad aggiungere un for(j=0;j<1000;j++)prima di ognuno forvedrai che (sotto chrome) la differenza di velocità si riduce. Alla fine era ancora 1.5x per la concatenazione di stringhe, ma più piccolo del 2.6 che era prima.

E dovendo copiare gli elementi, un carattere Unicode è probabilmente più piccolo di un riferimento a un oggetto JS.

Tieni presente che esiste la possibilità che molte implementazioni di motori JS abbiano un'ottimizzazione per array di tipo singolo che renderebbe inutile tutto ciò che ho scritto :-)


1

Questo test mostra la penalità dell'utilizzo effettivo di una stringa creata con la concatenazione di assegnazione rispetto a quella eseguita con il metodo array.join. Sebbene la velocità complessiva di assegnazione sia ancora due volte più veloce in Chrome v31, non è più così grande come quando non si utilizza la stringa risultante.


0

Questo dipende chiaramente dall'implementazione del motore javascript. Anche per diverse versioni di un motore è possibile ottenere risultati significativamente diversi. Dovresti fare il tuo benchmark per verificarlo.

Direi che String.concatha prestazioni migliori nelle recenti versioni di V8. Ma per Firefox e Opera, Array.joinè un vincitore.


-1

La mia ipotesi è che, sebbene ogni versione stia consumando il costo di molte concatenazioni, le versioni di join costruiscono array in aggiunta a quello.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.