Por que a concatenação de strings é mais rápida do que a junção de array?

114

Hoje, eu li este tópico sobre a velocidade da concatenação de strings.

Surpreendentemente, a concatenação de strings foi a vencedora:

http://jsben.ch/#/OJ3vo

O resultado foi contrário ao que eu pensava. Além disso, existem muitos artigos sobre este que explicam oposta como este .

Posso supor que os navegadores são otimizados para sequenciar concatna versão mais recente, mas como eles fazem isso? Podemos dizer que é melhor usar +ao concatenar strings?


Atualizar

Portanto, em navegadores modernos, a concatenação de strings é otimizada, portanto, usar +sinais é mais rápido do que joinquando você deseja concatenar strings.

Mas @Arthur apontou que joiné mais rápido se você realmente quiser juntar strings com um separador.


Atualização - 2020
Chrome: Array joinquase 2 times fasteré String concat + Consulte: https://stackoverflow.com/a/54970240/984471

Como nota:

  • Array joiné melhor se você tiverlarge strings
  • Se precisarmos gerar several small stringsna saída final, é melhor ir com string concat +, caso contrário, ir com Array precisará de várias conversões de Array para String no final, o que é uma sobrecarga de desempenho.

Sanghyun Lee
fonte
1
Esse código deve produzir 500 terabytes de lixo, mas é executado em 200 ms. Eu acho que eles apenas alocam um pouco mais de espaço para uma string, e quando você adiciona uma string curta a ela, geralmente ela cabe em um espaço extra.
Ivan Kuckir

Respostas:

149

As otimizações de string do navegador mudaram a imagem de concatenação de string.

O Firefox foi o primeiro navegador a otimizar a concatenação de strings. A partir da versão 1.0, a técnica de array é realmente mais lenta do que usar o operador mais em todos os casos. Outros navegadores também otimizaram a concatenação de strings, de modo que Safari, Opera, Chrome e Internet Explorer 8 também apresentam melhor desempenho usando o operador plus. O Internet Explorer anterior à versão 8 não tinha essa otimização e, portanto, a técnica de array é sempre mais rápida que o operador plus.

- Escrevendo JavaScript eficiente: Capítulo 7 - Sites ainda mais rápidos

O mecanismo javascript V8 (usado no Google Chrome) usa este código para fazer concatenação de strings:

// 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, "");
}

Então, internamente eles o otimizam criando um InternalArray (a partsvariável), que é então preenchido. A função StringBuilderConcat é chamada com essas partes. É rápido porque a função StringBuilderConcat é um código C ++ altamente otimizado. É muito longo para citar aqui, mas procure no arquivo runtime.cc para RUNTIME_FUNCTION(MaybeObject*, Runtime_StringBuilderConcat)ver o código.

Daan
fonte
4
Você deixou o realmente interessante de fora, o array é usado apenas para chamar Runtime_StringBuilderConcat com contagens de argumentos diferentes. Mas o verdadeiro trabalho é feito lá.
evilpie
41
Otimização 101: Você deve buscar o menos lento! por exemplo, arr.join vs str+no cromo você obtém (em operações por segundo) 25k/s vs 52k/s. no Firefox novo você começa 76k/s vs 212k/s. então str+é MAIS RÁPIDO. mas vamos dar uma olhada em outros navegadores. O Opera oferece 43k / s contra 26k / s. IE dá 1300/s vs 1002/s. veja o que acontece? o único navegador que precisa de otimização seria melhor usar o que é mais lento em todos os outros, onde isso não importa em nada. Portanto, nenhum desses artigos entende nada sobre desempenho.
gcb
45
@gcb, os únicos navegadores para os quais o join é mais rápido não devem ser usados. 95% dos meus usuários têm FF e Chrome. Vou otimizar para o caso de uso de 95%.
Paul Draper
7
@PaulDraper se 90% dos usuários estiverem em um navegador rápido e qualquer opção que você escolher irá obter 0,001s, mas 10% dos seus usuários ganharão 2s se você escolher penalizar os outros usuários com aqueles 0,001s ... a decisão está claro. se você não consegue ver, sinto muito pela pessoa para quem você codificou.
gcb
7
Navegadores mais antigos eventualmente desaparecerão, mas as chances de alguém voltar para converter todas essas junções de array não são prováveis. É melhor codificar para o futuro, desde que não seja um grande inconveniente para os usuários atuais. É provável que haja coisas mais importantes com que se preocupar do que o desempenho da concatenação ao lidar com navegadores antigos.
Thomas Higginbotham
23

O Firefox é rápido porque usa algo chamado Ropes ( Ropes: an Alternative to Strings ). Uma corda é basicamente apenas um DAG, onde cada Nó é uma corda.

Então, por exemplo, se você fizer isso a = 'abc'.concat('def'), o objeto recém-criado terá esta aparência. Claro que não é exatamente assim que parece na memória, porque você ainda precisa ter um campo para o tipo de string, comprimento e talvez outro.

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

E b = a.concat('123')

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

Portanto, no caso mais simples, a VM quase não precisa fazer nenhum trabalho. O único problema é que isso retarda um pouco outras operações na string resultante. Isso também reduz a sobrecarga de memória.

Por outro lado ['abc', 'def'].join(''), normalmente apenas alocaria memória para dispor a nova string plana na memória. (Talvez isso deva ser otimizado)

torta do mal
fonte
6

Eu sei que este é um tópico antigo, mas seu teste está incorreto. Você está fazendo output += myarray[i];enquanto deveria ser mais como output += "" + myarray[i];porque você esqueceu, que você tem que colar itens com alguma coisa. O código concat deve ser algo como:

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

Dessa forma, você está fazendo duas operações em vez de uma devido à colagem de elementos.

Array.join() é mais rápido.

Arthur
fonte
Não recebo sua resposta. Qual é a diferença entre colocar "" +e o original?
Sanghyun Lee
São duas operações em vez de uma em cada iteração, o que leva mais tempo.
Arthur
1
E por que precisamos colocar isso? Já estamos colando itens outputsem ele.
Sanghyun Lee
Porque é assim que o join funciona. Por exemplo, você também pode fazer o Array.join(",")que não funcionará com seu forloop
Arthur
Ah, entendi. Você já testou para ver se join () é mais rápido?
Sanghyun Lee
5

Para uma grande quantidade de dados, a junção é mais rápida, então a questão é declarada incorretamente.

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

Testado no Chrome 72.0.3626.119, Firefox 65.0.1, Edge 42.17134.1.0. Observe que é mais rápido mesmo com a criação de array incluída!

quase um
fonte
~ Agosto de 2020. Verdadeiro. No Chrome: Array Join time: 462. String Concat (+) time: 827. Join é quase 2 vezes mais rápido.
Manohar Reddy Poreddy
3

Os benchmarks lá são triviais. Concatenar os mesmos três itens repetidamente será embutido, os resultados serão comprovadamente determinísticos e memoizados, o manipulador de lixo estará apenas jogando fora os objetos de array (que terão quase nada em tamanho) e provavelmente apenas empurrados e retirados da pilha devido a nenhum referências externas e porque as strings nunca mudam. Eu ficaria mais impressionado se o teste fosse um grande número de strings geradas aleatoriamente. Como em um show ou dois de cordas.

Array.join FTW!

Jeremy Moritz
fonte
2

Eu diria que com strings é mais fácil pré-alocar um buffer maior. Cada elemento tem apenas 2 bytes (se UNICODE), então mesmo se você for conservador, você pode pré-alocar um buffer bem grande para a string. Com arrayscada elemento é mais "complexo", porque cada elemento é um Object, então uma implementação conservadora irá pré-alocar espaço para menos elementos.

Se você tentar adicionar um for(j=0;j<1000;j++)antes de cada um, forverá que (sob o cromo) a diferença na velocidade fica menor. No final, ainda era 1,5x para a concatenação de strings, mas menor do que 2,6 vezes antes.

E tendo que copiar os elementos, um caractere Unicode é provavelmente menor do que uma referência a um objeto JS.

Esteja ciente de que existe a possibilidade de que muitas implementações de mecanismos JS tenham uma otimização para matrizes de tipo único que tornaria tudo o que escrevi inútil :-)

xanatos
fonte
1

Este teste mostra a penalidade de realmente usar uma string feita com concatenação de atribuição versus feita com o método array.join. Embora a velocidade geral de atribuição ainda seja duas vezes mais rápida no Chrome v31, ela não é mais tão grande quanto quando não se usa a string resultante.

srgstm
fonte
0

Isso depende claramente da implementação do mecanismo javascript. Mesmo para versões diferentes de um motor, você pode obter resultados significativamente diferentes. Você deve fazer seu próprio benchmark para verificar isso.

Eu diria que String.concattem melhor desempenho nas versões recentes do V8. Mas para Firefox e Opera, Array.joiné um vencedor.

Vanuan
fonte
-1

Meu palpite é que, embora cada versão esteja usando o custo de muitas concatenações, as versões de junção estão criando matrizes além disso.

Marcelo Cantos
fonte