¿Por qué la concatenación de cadenas es más rápida que la combinación de matrices?


114

Hoy, leí este hilo sobre la velocidad de la concatenación de cadenas.

Sorprendentemente, la concatenación de cadenas fue la ganadora:

http://jsben.ch/#/OJ3vo

El resultado fue contrario a lo que pensaba. Además, hay muchos artículos sobre esto que explican lo contrario de esta manera .

Puedo suponer que los navegadores están optimizados para encadenar concaten la última versión, pero ¿cómo lo hacen? ¿Podemos decir que es mejor usarlo +al concatenar cadenas?


Actualizar

Entonces, en los navegadores modernos, la concatenación de cadenas está optimizada, por lo que usar +signos es más rápido que usar joincuando desea concatenar cadenas.

Pero @Arthur señaló que joines más rápido si realmente desea unir cadenas con un separador.


Actualización - 2020
Chrome: Array joincasi 2 times fasteres String concat + Consulte: https://stackoverflow.com/a/54970240/984471

Como nota:

  • Array joines mejor si tieneslarge strings
  • Si necesitamos generar several small stringsen la salida final, es mejor ir con string concat +, ya que de lo contrario, ir con Array necesitará varias conversiones de Array a String al final, lo cual es una sobrecarga de rendimiento.


1
Se supone que este código produce 500 terabytes de basura, pero se ejecuta en 200 ms. Creo que solo asignan un poco más de espacio para una cuerda, y cuando le agregas una cuerda corta, generalmente cabe en un espacio adicional.
Ivan Kuckir

Respuestas:


149

Las optimizaciones de cadenas del navegador han cambiado la imagen de la concatenación de cadenas.

Firefox fue el primer navegador en optimizar la concatenación de cadenas. A partir de la versión 1.0, la técnica de matriz es más lenta que usar el operador más en todos los casos. Otros navegadores también han optimizado la concatenación de cadenas, por lo que Safari, Opera, Chrome e Internet Explorer 8 también muestran un mejor rendimiento con el operador más. Internet Explorer anterior a la versión 8 no tenía tal optimización, por lo que la técnica de matriz es siempre más rápida que el operador plus.

- Escritura de JavaScript eficiente: Capítulo 7 - Sitios web aún más rápidos

El motor javascript V8 (utilizado en Google Chrome) utiliza este código para realizar la concatenación de cadenas:

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

Entonces, internamente lo optimizan creando un InternalArray (la partsvariable), que luego se llena. La función StringBuilderConcat se llama con estas partes. Es rápido porque la función StringBuilderConcat es un código C ++ muy optimizado. Es demasiado largo para citar aquí, pero busque en el archivo runtime.cc para RUNTIME_FUNCTION(MaybeObject*, Runtime_StringBuilderConcat)ver el código.


4
Dejó lo realmente interesante, la matriz solo se usa para llamar a Runtime_StringBuilderConcat con diferentes recuentos de argumentos. Pero el trabajo real se hace allí.
evilpie

41
Optimización 101: ¡Debes apuntar a lo menos lento! por ejemplo, arr.join vs str+en Chrome obtienes (en operaciones por segundo) 25k/s vs 52k/s. en firefox nuevo obtienes 76k/s vs 212k/s. también str+es MÁS RÁPIDO. pero veamos otros navegadores. Opera da 43k / s frente a 26k / s. IE da 1300/s vs 1002/s. ¿mira qué pasa? el único navegador que NECESITA optimización estaría mejor usando el que es más lento en todos los demás, donde no importa en absoluto. Entonces, ninguno de esos artículos comprende nada sobre el rendimiento.
gcb

45
@gcb, los únicos navegadores para los que la unión es más rápida no deberían usarse. El 95% de mis usuarios tienen FF y Chrome. Voy a optimizar para el caso de uso del 95%.
Paul Draper

7
@PaulDraper si el 90% de los usuarios están en un navegador rápido y cualquiera de las opciones que elijas les dará 0.001s, pero el 10% de tus usuarios ganarán 2s si eliges penalizar a los otros usuarios de esos 0.001s ... la decisión es claro. Si no puede verlo, lo siento por quien sea que codifique.
gcb

7
Los navegadores más antiguos eventualmente desaparecerán, pero es poco probable que alguien vuelva a convertir todas esas combinaciones de matrices. Es mejor codificar para el futuro siempre que no sea un inconveniente importante para los usuarios actuales. Lo más probable es que haya cosas más importantes de las que preocuparse que el rendimiento de la concatenación cuando se trata de navegadores antiguos.
Thomas Higginbotham

23

Firefox es rápido porque usa algo llamado Ropes ( Ropes: an Alternative to Strings ). Una cuerda es básicamente un DAG, donde cada nodo es una cuerda.

Entonces, por ejemplo, si lo hiciera a = 'abc'.concat('def'), el objeto recién creado se vería así. Por supuesto, esto no es exactamente cómo se ve esto en la memoria, porque aún necesita tener un campo para el tipo de cadena, la longitud y tal vez otros.

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

Y b = a.concat('123')

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

Entonces, en el caso más simple, la VM casi no tiene que hacer ningún trabajo. El único problema es que esto ralentiza un poco otras operaciones en la cadena resultante. También esto, por supuesto, reduce la sobrecarga de memoria.

Por otro lado ['abc', 'def'].join(''), normalmente solo asignaría memoria para colocar la nueva cadena plana en la memoria. (Quizás esto debería optimizarse)


6

Sé que este es un hilo antiguo, pero tu prueba es incorrecta. Lo estás haciendo output += myarray[i];mientras debería ser más bien output += "" + myarray[i];porque te olvidaste que tienes que pegar elementos con algo. El código de concat debería ser algo como:

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

De esa manera, está haciendo dos operaciones en lugar de una debido a pegar elementos.

Array.join() es más rápido.


No recibo tu respuesta. ¿Cuál es la diferencia entre el putt "" +y el original?
Sanghyun Lee

Son dos operaciones en lugar de una en cada iteración, lo que lleva más tiempo.
Arthur

1
¿Y por qué tenemos que poner eso? Ya estamos pegando artículos outputsin él.
Sanghyun Lee

Porque así es como funciona join. Por ejemplo, también puede hacer Array.join(",")lo que no funcionará con su forbucle
Arthur

Oh lo tengo. ¿Ya ha probado para ver si join () es más rápido?
Sanghyun Lee

5

Para una gran cantidad de datos, la combinación es más rápida, por lo que la pregunta se formula incorrectamente.

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

Probado en Chrome 72.0.3626.119, Firefox 65.0.1, Edge 42.17134.1.0. ¡Tenga en cuenta que es más rápido incluso con la creación de matrices incluida!


~ Agosto de 2020. Verdadero. En Chrome: Array Join time: 462. String Concat (+) time: 827. Join es casi 2 veces más rápido.
Manohar Reddy Poreddy

3

Los puntos de referencia son triviales. La concatenación de los mismos tres elementos repetidamente se incluirá en línea, los resultados demostrarán ser deterministas y memorizados, el controlador de basura simplemente arrojará objetos de matriz (que tendrán un tamaño casi nulo) y probablemente simplemente empujará y saldrá de la pila debido a que no referencias externas y porque las cadenas nunca cambian. Me impresionaría más si la prueba fuera una gran cantidad de cadenas generadas aleatoriamente. Como en un concierto o dos de cuerdas.

¡Únete a FTW!


2

Yo diría que con cadenas es más fácil preasignar un búfer más grande. Cada elemento tiene solo 2 bytes (si es UNICODE), por lo que incluso si es conservador, puede preasignar un búfer bastante grande para la cadena. Con arrayscada elemento es más "complejo", porque cada elemento es un Object, por lo que una implementación conservadora preasignará espacio para menos elementos.

Si intenta agregar un for(j=0;j<1000;j++)antes de cada foruno, verá que (debajo de cromo) la diferencia de velocidad se reduce. Al final, todavía era 1.5x para la concatenación de cadenas, pero más pequeño que el 2.6 que era antes.

Y teniendo que copiar los elementos, un carácter Unicode probablemente sea más pequeño que una referencia a un objeto JS.

Tenga en cuenta que existe la posibilidad de que muchas implementaciones de motores JS tengan una optimización para matrices de un solo tipo que haría que todo lo que he escrito sea inútil :-)


1

Esta prueba muestra la penalización de usar realmente una cadena hecha con concatenación de asignaciones versus hecha con el método array.join. Si bien la velocidad general de asignación sigue siendo dos veces más rápida en Chrome v31, ya no es tan grande como cuando no se usa la cadena resultante.


0

Esto depende claramente de la implementación del motor javascript. Incluso para diferentes versiones de un motor, puede obtener resultados significativamente diferentes. Debe hacer su propio punto de referencia para verificar esto.

Yo diría que String.concattiene mejor rendimiento en las últimas versiones de V8. Pero para Firefox y Opera, Array.joines un ganador.


-1

Supongo que, si bien cada versión tiene el costo de muchas concatenaciones, las versiones de unión están construyendo matrices además de eso.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.