¿El uso de funciones anónimas afecta el rendimiento?

89

Me he estado preguntando, ¿existe una diferencia de rendimiento entre el uso de funciones con nombre y funciones anónimas en Javascript?

for (var i = 0; i < 1000; ++i) {
    myObjects[i].onMyEvent = function() {
        // do something
    };
}

vs

function myEventHandler() {
    // do something
}

for (var i = 0; i < 1000; ++i) {
    myObjects[i].onMyEvent = myEventHandler;
}

El primero es más ordenado, ya que no abarrota su código con funciones que rara vez se utilizan, pero ¿importa que vuelva a declarar esa función varias veces?

nickf
fuente
Sé que no está en la pregunta, pero con respecto a la limpieza / legibilidad del código, creo que la 'forma correcta' está en algún punto intermedio. El "desorden" de funciones de nivel superior que rara vez se usan es molesto, pero también lo es el código fuertemente anidado que depende mucho de funciones anónimas que se declaran en línea con su invocación (piense en el infierno de devolución de llamada de node.js). Tanto el primero como el último pueden dificultar el seguimiento de la depuración / ejecución.
Zac B
Las siguientes pruebas de rendimiento ejecutan la función durante miles de iteraciones. Incluso si ve una diferencia sustancial, la mayoría de los casos de uso no harán esto en iteraciones de ese orden. Por lo tanto, es mejor elegir lo que se adapte a sus necesidades e ignorar el rendimiento para este caso en particular.
usuario
@nickf, por supuesto, es una pregunta demasiado antigua, pero vea la nueva respuesta actualizada
Chandan Pasunoori

Respuestas:

89

El problema de rendimiento aquí es el costo de crear un nuevo objeto de función en cada iteración del ciclo y no el hecho de que use una función anónima:

for (var i = 0; i < 1000; ++i) {    
    myObjects[i].onMyEvent = function() {
        // do something    
    };
}

Está creando mil objetos de función distintos a pesar de que tienen el mismo cuerpo de código y no se vinculan al ámbito léxico ( cierre ). Lo siguiente parece más rápido, por otro lado, porque simplemente asigna la misma referencia de función a los elementos de la matriz en todo el ciclo:

function myEventHandler() {
    // do something
}

for (var i = 0; i < 1000; ++i) {
    myObjects[i].onMyEvent = myEventHandler;
}

Si tuviera que crear la función anónima antes de ingresar al bucle, entonces solo asigne referencias a los elementos de la matriz mientras está dentro del bucle, encontrará que no hay diferencia de rendimiento o semántica en comparación con la versión de la función nombrada:

var handler = function() {
    // do something    
};
for (var i = 0; i < 1000; ++i) {    
    myObjects[i].onMyEvent = handler;
}

En resumen, no hay un costo de rendimiento observable al usar funciones anónimas sobre funciones nombradas.

Como acotación al margen, puede parecer desde arriba que no hay diferencia entre:

function myEventHandler() { /* ... */ }

y:

var myEventHandler = function() { /* ... */ }

La primera es una declaración de función, mientras que la última es una asignación variable a una función anónima. Aunque parezca que tienen el mismo efecto, JavaScript los trata de manera ligeramente diferente. Para comprender la diferencia, recomiendo leer, " Ambigüedad de declaración de función de JavaScript ".

El tiempo de ejecución real de cualquier enfoque dependerá en gran medida de la implementación del compilador y el tiempo de ejecución del navegador. Para obtener una comparación completa del rendimiento del navegador moderno, visite el sitio JS Perf

Atif Aziz
fuente
Olvidó los paréntesis antes del cuerpo de la función. Lo acabo de probar, son obligatorios.
Chinoto Vokro
¡Parece que los resultados del benchmark dependen mucho del motor js!
aleclofabbro
3
¿No hay una falla en el ejemplo de JS Perf: el caso 1 solo define la función, mientras que los casos 2 y 3 parecen llamar accidentalmente a la función?
bluenote10
Entonces, usando este razonamiento, ¿significa que al desarrollar node.jsaplicaciones web, es mejor crear las funciones fuera del flujo de solicitud y pasarlas como devoluciones de llamada, que crear devoluciones de llamada anónimas?
Xavier T Mukodi
23

Aquí está mi código de prueba:

var dummyVar;
function test1() {
    for (var i = 0; i < 1000000; ++i) {
        dummyVar = myFunc;
    }
}

function test2() {
    for (var i = 0; i < 1000000; ++i) {
        dummyVar = function() {
            var x = 0;
            x++;
        };
    }
}

function myFunc() {
    var x = 0;
    x++;
}

document.onclick = function() {
    var start = new Date();
    test1();
    var mid = new Date();
    test2();
    var end = new Date();
    alert ("Test 1: " + (mid - start) + "\n Test 2: " + (end - mid));
}

Los resultados:
Prueba 1: 142 ms Prueba 2: 1983 ms

Parece que el motor JS no reconoce que es la misma función en Test2 y la compila cada vez.

nickf
fuente
3
¿En qué navegador se realizó esta prueba?
andynil
5
Tiempos para mí en Chrome 23: (2ms / 17ms), IE9: (20ms / 83ms), FF 17: (2ms / 96ms)
Davy8
Tu respuesta merece más peso. Mis tiempos en Intel i5 4570S: Chrome 41 (1/9), IE11 (1/25), FF36 (1/14). Claramente, la función anónima en un bucle funciona peor.
ThisClark
3
Esta prueba no es tan útil como parece. En ninguno de los ejemplos se está ejecutando realmente la función interior. Efectivamente, todo lo que muestra esta prueba es que crear una función 10000000 veces es más rápido que crear una función una vez.
Nucleon
2

Como principio de diseño general, debe evitar la implementación del mismo código varias veces. En su lugar, debe extraer el código común en una función y ejecutar esa función (general, bien probada, fácil de modificar) desde varios lugares.

Si (a diferencia de lo que infiere de su pregunta) está declarando la función interna una vez y usando ese código una vez (y no tiene nada más idéntico en su programa), entonces una función anónima probablemente (eso es una suposición) será tratada de la misma manera por el compilador como una función con nombre normal.

Es una característica muy útil en casos específicos, pero no debería usarse en muchas situaciones.

Tom Leys
fuente
1

No esperaría mucha diferencia, pero si hay una, probablemente variará según el motor de secuencias de comandos o el navegador.

Si encuentra el código más fácil de asimilar, el rendimiento no es un problema a menos que espere llamar a la función millones de veces.

Joe Skora
fuente
1

Donde podemos tener un impacto en el rendimiento es en la operación de declaración de funciones. Aquí hay un punto de referencia para declarar funciones dentro del contexto de otra función o fuera:

http://jsperf.com/function-context-benchmark

En Chrome el funcionamiento es más rápido si declaramos la función en el exterior, pero en Firefox es todo lo contrario.

En otro ejemplo vemos que si la función interna no es una función pura, tendrá una falta de rendimiento también en Firefox: http://jsperf.com/function-context-benchmark-3

Pablo Estornut
fuente
0

Lo que definitivamente hará que su bucle sea más rápido en una variedad de navegadores, especialmente los navegadores IE, es el siguiente bucle:

for (var i = 0, iLength = imgs.length; i < iLength; i++)
{
   // do something
}

Ha puesto un 1000 arbitrario en la condición de bucle, pero obtiene mi deriva si desea revisar todos los elementos de la matriz.

Sarhanis
fuente
0

una referencia casi siempre será más lenta que la cosa a la que se refiere. Piénselo de esta manera: digamos que desea imprimir el resultado de sumar 1 + 1. Lo que tiene más sentido:

alert(1 + 1);

o

a = 1;
b = 1;
alert(a + b);

Me doy cuenta de que es una forma realmente simplista de verlo, pero es ilustrativo, ¿verdad? Use una referencia solo si se va a usar varias veces, por ejemplo, cuál de estos ejemplos tiene más sentido:

$(a.button1).click(function(){alert('you clicked ' + this);});
$(a.button2).click(function(){alert('you clicked ' + this);});

o

function buttonClickHandler(){alert('you clicked ' + this);}
$(a.button1).click(buttonClickHandler);
$(a.button2).click(buttonClickHandler);

La segunda es una mejor práctica, incluso si tiene más líneas. Ojalá todo esto sea de ayuda. (y la sintaxis de jquery no confundió a nadie)

Matt Lohkamp
fuente
0

@nickf

(Ojalá tuviera el representante para comentar, pero acabo de encontrar este sitio)

Mi punto es que hay confusión aquí entre funciones nombradas / anónimas y el caso de uso de ejecutar + compilar en una iteración. Como ilustré, la diferencia entre anon + named es insignificante en sí misma; estoy diciendo que es el caso de uso el que está defectuoso.

Me parece obvio, pero si no, creo que el mejor consejo es "no hagas tonterías" (uno de los cuales es el cambio constante de bloques + la creación de objetos de este caso de uso) y, si no estás seguro, ¡prueba!

Annakata
fuente
0

¡SI! Las funciones anónimas son más rápidas que las funciones normales. Quizás si la velocidad es de suma importancia ... más importante que la reutilización del código, entonces considere usar funciones anónimas.

Hay un artículo realmente bueno sobre cómo optimizar javascript y funciones anónimas aquí:

http://dev.opera.com/articles/view/efficient-javascript/?page=2

Christopher Tokar
fuente
0

Los objetos anónimos son más rápidos que los objetos con nombre. Pero llamar a más funciones es más caro y hasta un punto que eclipsa cualquier ahorro que pueda obtener mediante el uso de funciones anónimas. Cada función llamada se suma a la pila de llamadas, lo que introduce una pequeña pero no trivial cantidad de sobrecarga.

Pero a menos que esté escribiendo rutinas de cifrado / descifrado o algo similarmente sensible al rendimiento, como muchos otros han señalado, siempre es mejor optimizar para un código elegante y fácil de leer en lugar de un código rápido.

Suponiendo que está escribiendo un código bien diseñado, los problemas de velocidad deberían ser responsabilidad de quienes escriben los intérpretes / compiladores.

pcorcoran
fuente
0

@nickf

Sin embargo, esa es una prueba bastante fatua, está comparando el tiempo de ejecución y compilación allí, lo que obviamente va a costar el método 1 (compila N veces, dependiendo del motor JS) con el método 2 (compila una vez). No puedo imaginar a un desarrollador de JS que pasaría su código de escritura de prueba de esa manera.

Un enfoque mucho más realista es la asignación anónima, ya que de hecho está utilizando el método document.onclick es más parecido al siguiente, que de hecho favorece levemente el método anon.

Usando un marco de prueba similar al suyo:


function test(m)
{
    for (var i = 0; i < 1000000; ++i) 
    {
        m();
    }
}

function named() {var x = 0; x++;}

var test1 = named;

var test2 = function() {var x = 0; x++;}

document.onclick = function() {
    var start = new Date();
    test(test1);
    var mid = new Date();
    test(test2);
    var end = new Date();
    alert ("Test 1: " + (mid - start) + "ms\n Test 2: " + (end - mid) + "ms");
}
Annakata
fuente
0

Como se señaló en los comentarios a la respuesta de @nickf: La respuesta a

Es crear una función una vez más rápido que crearla un millón de veces

es simplemente sí. Pero como muestra su rendimiento JS, no es más lento en un factor de un millón, lo que demuestra que en realidad se vuelve más rápido con el tiempo.

La pregunta más interesante para mí es:

¿Cómo funciona un repiten crear + carrera comparar + para crear una vez repetida ejecutar .

Si una función realiza un cálculo complejo, el tiempo necesario para crear el objeto de función probablemente sea insignificante. Pero, ¿qué pasa con los gastos generales de creación en los casos en que la ejecución es rápida? Por ejemplo:

// Variant 1: create once
function adder(a, b) {
  return a + b;
}
for (var i = 0; i < 100000; ++i) {
  var x = adder(412, 123);
}

// Variant 2: repeated creation via function statement
for (var i = 0; i < 100000; ++i) {
  function adder(a, b) {
    return a + b;
  }
  var x = adder(412, 123);
}

// Variant 3: repeated creation via function expression
for (var i = 0; i < 100000; ++i) {
  var x = (function(a, b) { return a + b; })(412, 123);
}

Este JS Perf muestra que crear la función solo una vez es más rápido de lo esperado. Sin embargo, incluso con una operación muy rápida como una simple adición, la sobrecarga de crear la función repetidamente es solo un pequeño porcentaje.

La diferencia probablemente solo se vuelve significativa en los casos en que la creación del objeto de función es compleja, mientras se mantiene un tiempo de ejecución insignificante, por ejemplo, si todo el cuerpo de la función está envuelto en un if (unlikelyCondition) { ... }.

bluenote10
fuente