Búsqueda de sitios web

Optimización de Javascript con `new Function()`


Mientras leía la documentación, encontré una simple optimización que mejora enormemente el rendimiento de JavaScript.

Código original:

function parseRow(columns, parser) {
  var row = {};
  for (var i = 0; i < columns.length; i++) {
    row[columns[i].name] = parser.readColumnValue();
  }
}

Código optimizado :

var code = 'return {\n';
columns.forEach(function(column) {
  code += '"' + column.name + '":' + 'parser.readColumnValue(),\n';
});
code += '};\n';

var parseRow = new Function('columns', 'parser', code);

Se encuentra aquí: https://github.com/felixge/faster-than-c
¿Por qué se ejecuta un 20% más rápido?
Creo que elimina la instrucción for, pero ¿no tiene el forEach el mismo costo computacional?

Respuesta #1

La diferencia es que solo estás usando forEach para construir la función optimizada. Una vez que la función se crea, no hay ningún bucle dentro: el bucle se despliega y los nombres de las columnas están codificados. Luego, el método se evala en una función funcional, que incluso podría ser compilada en código máquina, dependiendo del motor. Esto resulta en dos mejoras de rendimiento:

  1. Al eliminar por completo la comprobación de la condición del bucle for (i < columns.length), no hay ramificaciones, y
  2. Al codificar valores de column[i].name en múltiples declaraciones, eliminaste la evaluación de column[i] y las búsquedas en column.name en cada paso.

Entonces, después de llamar a new Function(...) con el código pasado como un String, tu variable parseRow obtiene la referencia a la siguiente función:

function parseRow(columns, parser) {
    return {
        "columnOne": parser.readColumnValue(),
        "columnTwo": parser.readColumnValue(),
        "columnThree": parser.readColumnValue(),
        ...
    };
}

Tenga en cuenta que no hay bucles, bifurcaciones u otras búsquedas en ese código, excepto por las múltiples llamadas a parser.readColumnValue().

¿Por qué es esto posible en JavaScript?

La razón por la que esto funciona tan eficientemente en JavaScript es porque el código fuente de JavaScript en cualquier página web necesita ser interpretado o compilado por el motor de JS de todos modos. No envías tu página web con ejecutables compilados, ni siquiera con bytecode (algo) precompilado (como Java o .NET). Cada vez que se carga un nuevo archivo .js, tu navegador lo compilará desde cero antes de ejecutarlo (bueno, para ser precisos, en los motores modernos es algo entre interpretar y compilar, es decir, JIT).

Esto significa que crear una función que funcione a partir de una cadena (es decir, compilar el código) durante el tiempo de ejecución no es menos eficiente que tener código escrito a mano siendo leído desde el archivo .js. Compárelo con un programa en C/C++, que en todos los casos razonables se compila a código máquina (es decir, archivo ejecutable que está lo más cerca posible de la CPU) antes de llegar al cliente.

Si quisieras hacer esto en C++ (una especie de código auto-modificable), tendrías que incluir un compilador junto con tu aplicación para construir el código, y el costo de construir esta función superaría los beneficios que obtendrías cuando finalmente la iniciaras. En .NET, por ejemplo, tampoco es inusual que un programa emita métodos o incluso ensamblados en tiempo de ejecución, los cuales luego se compilan JIT a código máquina, lo que permite posibles mejoras de rendimiento como la que planteas en tu pregunta.

Respuesta #2

Las ganancias de rendimiento dependen mucho del motor de JavaScript, así como de los datos que se están procesando. No sabemos las circunstancias exactas de "20% más rápido" (excepto por usar node.js). Podría ser más lento en algunas situaciones. (Editar: Necesitarías llamar a la función con suficiente frecuencia para compensar el costo de construcción). Algunas posibles razones de las ganancias:

El código optimizado crea un literal de objeto. La versión anterior asigna constantemente valores a propiedades que aún no existen. Eso tiene un costo asociado.

row[columns[i].name] tiene tres búsquedas, mientras que la versión optimizada no tiene ninguna, una vez que la función está construida. Y no olvides que row[columns[i].name] aún no existe, por lo que la búsqueda es más costosa. columns.length también es una búsqueda.