Exploración del rendimiento de la multiplicación de cadenas de JavaScript

Dado que JavaScript concatena cadenas con el + operador, sería ingenioso si también le permitiera multiplicar cadenas usando, p. str * 10 (como se puede hacer en Python, al menos). Como no puede hacer eso, y no se proporciona ningún método nativo de multiplicación de cadenas, recientemente exploré algunas formas de lograrlo...

Un enfoque ingenuo para escribir una función multiplicadora de cadenas es algo como esto:

function mul0 (str, num) {
	if (!num) return "";
	var newStr = str;
	while (--num) newStr += str;
	return newStr;
}

Como saben muchos usuarios de JavaScript, ese no es el mejor enfoque, ya que la concatenación de cadenas puede ser bastante lenta en Internet Explorer. Y aunque IE tiende a tener una mala reputación por esto (afortunadamente, el equipo de IE está solucionando el problema en la próxima versión de su navegador), Firefox tampoco es muy rápido en la concatenación de cadenas. Debido a los problemas de rendimiento, el enfoque típico de multiplicación de cadenas es construir una matriz y join eso. Esta es una manera agradable y breve de hacerlo:

function mul1 (str, num) {
	return num ? Array(num + 1).join(str) : "";
}

Tenga en cuenta que el falso num el manejo probablemente no esté garantizado en este caso ya que la función manejaría el valor 0 correctamente sin ella. Se hace de todos modos para mantener la funcionalidad equivalente en todas las variaciones.

Desafortunadamente, mul1 todavía puede ser bastante lento en Firefox 2 cuando se multiplican cadenas grandes muchas veces. Puede pasar desapercibido con cadenas pequeñas y números de repetición, pero el tiempo de finalización aumenta a un ritmo superlineal a medida que aumentan los números. En busca de una solución más rápida, intenté usar una expresión regular para mantener bajo el tamaño de la cadena con la que se trabajaba:

var mul2 = function () {
	function mul (str, num) {
		return Array(num + 1).join(str);
	}
	return function (str, num) {
		return num ? str.replace(/^/, mul("$'", num - 1)) : "";
	};
}();

Lo anterior multiplica la cadena de dos caracteres "$' " num - 1 veces, luego lo usa como reemplazo de una expresión regular que solo coincide con el comienzo de la cadena ($' devuelve el texto a la derecha de la coincidencia). ¿Cómo funciona eso? Funciona en Firefox 2 en mi sistema Windows Vista, con números como 95ms frente a 29800ms (mul1 ) cuando se usa un multiplicador/longitud de cadena de 2700x2700. Sin embargo, según mis pruebas, ese tipo de ganancia de velocidad parece estar limitada a Firefox, y en Safari 3 beta mul2 es considerablemente más lento que las versiones alternativas.

Finalmente, traté de crear una versión que multiplicaba la cadena a una tasa exponencial:

function mul3 (str, num) {
	if (!num) return "";
	var	orig = str,
		soFar = [str],
		added = 1,
		left, i;
	while (added < num) {
		left = num - added;
		str = orig;
		for (i = 2; i < left; i *= 2) {
			str += str;
		}
		soFar.push(str);
		added += (i / 2);
	}
	return soFar.join("");
}

Aunque eso podría ser más código del que está dispuesto a dedicar a un método de multiplicación de cadenas, es la más rápida de las versiones anteriores en un navegador cruzado promedio. También probé algunas variaciones usando de cero a dos matrices y varios métodos de matriz (push , concat , etc.), pero el anterior parece ser el más rápido en promedio entre los cuatro navegadores principales.

Asegúrese de probar las pruebas usted mismo y déjeme saber sus pensamientos y cómo mejoraría el código.

Editar: Kris Kowal contribuyó con mul4 (se muestra a continuación y se agregó a la página de prueba). Utiliza interpolación binaria y, en palabras de Kris, "aprovecha una identidad bit a bit divertida:(1 << n) == Math.pow(2, n) ". En mi sistema es significativamente más rápido que mul3 en Firefox, pero un poco más lento que mul3 en IE, Safari y Opera. Debido a su alta velocidad y peso más ligero, este parece ser el mejor. Pruebe la página de prueba en varios navegadores y vea lo que piensa.

function mul4 (str, num) {
	var acc = [];
	for (var i = 0; (1 << i) <= num; i++) {
		if ((1 << i) & num)
			acc.push(str);
		str += str;
	}
	return acc.join("");
}

Edición 2: LiuCougar del equipo de desarrollo de Dojo publicó un seguimiento que incluye varias variaciones adicionales, y David Andersson me envió por correo electrónico cuatro variaciones adicionales, incluida esta:

function mul8 (str, num) {
	var	i = Math.ceil(Math.log(num) / Math.LN2),
		res = str;
	do {
		res += res;
	} while (0 < --i);
	return res.slice(0, str.length * num);
}

Sin embargo, debo aclarar que esto es principalmente una discusión académica, ya que repetir los tipos de cadenas en la página de prueba tantas veces como lo hace es una idea bastante loca. Aún así, es divertido experimentar.

Edición 3: Todas las variaciones publicadas o enviadas por correo electrónico en respuesta a esta publicación se pueden ver en stevenlevithan.com/demo/mul/all.js . En aras de la coherencia, he realizado algunos ajustes menores en algunas de las funciones, como los ajustes de espacios en blanco y el cambio de nombre de los argumentos de entrada a str y num .