vanilla tips javascript jquery performance

javascript - vanilla - jquery performance tips



Rendimiento de jQuery.grep vs. Array.filter (5)

En una question , se discutió sobre cómo JQuery y JS nativo funcionarían uno contra el otro.

Si bien, por supuesto, la solución de vanilla funciona mucho más rápido porque no procesa toda la matriz, propuse el uso de Array.filter que estaba bastante seguro de que sería al menos más rápido que $.grep .

Sorprendentemente, después de agregarlo a la prueba, me enseñaron una lección: Testsuite

Edgecases por supuesto tienen un resultado diferente.

¿Alguien tiene una idea de por qué $.grep se supone que es más de 3 veces más rápido que el método nativo Arrray.filter ?

Editar: modifiqué la prueba para usar el filtro shim de MDN y los resultados son bastante interesantes:

  • Chrome: incluso MDN shim es más rápido que el método nativo, jQuery sigue adelante
  • Firefox: shim un poco más lento que el método nativo, jQuery camino por delante

y finalmente un resultado como esperaba ver en

  • Internet Explorer: el método nativo es el más rápido, luego jQuery, el shim es el más lento (quizás esto es solo el resultado de un motor JS bastante débil ...)

¿No está mal tu guión?

Para array.filter está haciendo la medición 1000 veces y la presenta por la suma dividida por 1000

Para JQuery.grep está haciendo la medición 1 vez y la presenta tomando la suma dividida por 1000.

Eso significaría que tu grep es en realidad 1000 veces más lento que el valor que usas para comparar.

Prueba rápida en firefox da:

Machine 1: average filter - 3.864 average grep - 4.472 Machine2: average filter - 1.086 average grep - 1.314

La prueba rápida en Chrome proporciona:

Machine 1: average filter - 69.095 average grep - 34.077 Machine2: average filter - 18.726 average grep - 9.163

La conclusión en firefox (50.0) es mucho más rápida para la ruta del código, y el filtro es aproximadamente un 10-15% más rápido que jquery.grep.

Chrome es extremadamente lento para la ruta de tu código, pero grep parece ser un 50% más rápido que array.filter por lo que es un 900% más lento que la ejecución de Firefox.


Como se encuentra en esta publicación de blog (que también hace el mismo tipo de pruebas):

Si lees la documentación para el filter , verás por qué es mucho más lento.

  1. Ignora los valores eliminados y las lagunas en la matriz
  2. Opcionalmente establece el contexto de ejecución de la función de predicado
  3. Impide que la función de predicado mute los datos

Descubrí algo interesante. Como explica MarcoK, $ .grep es solo una implementación simple con un bucle for. El filtro es más lento en la mayoría de los casos, por lo que la implementación debe ser diferente. Creo que encontré la respuesta:

function seak (e) { return e === 3; } var array = [1,2,3,4,5,6,7,8,9,0], i, before; array[10000] = 20; // This makes it slow, $.grep now has to iterate 10000 times. before = new Date(); // Perform natively a couple of times. for(i=0;i<10000;i++){ array.filter(seak); } document.write(''<div>took: '' + (new Date() - before) + ''</div>''); // took: 8515 ms (8s) before = new Date(); // Perform with JQuery a couple of times for(i=0;i<10000;i++){ $.grep(array, seak); } document.write(''<div>took: '' + (new Date() - before) + ''</div>''); // took: 51790 ms (51s)

El ''filtro'' nativo es mucho más rápido en este caso. Así que creo que itera las propiedades en lugar del índice de matriz.

Ahora volvamos a los "grandes" problemas;).


La sección 15.4.4.20 de la especificación ECMAScript 5.1 define Array.prototype.filter(callbackfn, thisArg) siguiente manera:

callbackfn debe ser una función que acepte tres argumentos y devuelva un valor que sea coercible al valor booleano true o false . filter llama a callbackfn una vez para cada elemento de la matriz, en orden ascendente, y construye una nueva matriz de todos los valores para los cuales callbackfn devuelve true . callbackfn se llama solo para los elementos de la matriz que realmente existen; no se pide que falten elementos de la matriz.

Si se proporciona un parámetro thisArg , se usará como this valor para cada invocación de callbackfn . Si no se proporciona, se usa undefined su lugar.

callbackfn se llama con tres argumentos: el valor del elemento, el índice del elemento y el objeto que se está recorriendo.

filter no muta directamente el objeto al que se llama, pero el objeto puede ser mutado por las llamadas a callbackfn .

El rango de elementos procesados ​​por filtro se establece antes de la primera llamada a callbackfn . Los elementos que se agregan a la matriz después de que comience la llamada al filtro no serán visitados por callbackfn . Si se cambian los elementos existentes de la matriz, su valor, tal como pasó a callbackfn , será el valor en el momento en que el filtro los visite; los elementos que se eliminan después de que comience la llamada al filtro y antes de ser visitados no se visitan.

Eso en sí mismo ya es mucho trabajo; una gran cantidad de pasos que el motor ECMAScript debe realizar.

Luego continúa diciendo lo siguiente:

Cuando se llama al método de filtro con uno o dos argumentos, se toman los siguientes pasos:

Deje O ser el resultado de llamar a ToObject pasando this valor como argumento. Deje que lenValue sea ​​el resultado de llamar al método interno de [[Get]] de O con la length argumento. Deje que len sea ToUint32(lenValue) . Si IsCallable (callbackfn) es falso, ejecute una excepción TypeError. Si se proporcionó este Arr, deje que T sea thisArg; de lo contrario, deje que T esté indefinido. Deje que A sea una nueva matriz creada como si fuera por la expresión new Array () donde Array es el constructor incorporado estándar con ese nombre. Deje k sea 0. Deje ser 0. Repita, mientras k <len Let Pk sea ToString (k). Deje que kPresent sea el resultado de llamar al método interno de [[HasProperty]] de O con el argumento Pk. Si kPresent es verdadero, entonces Let kValue sea el resultado de llamar al método interno [[Get]] de O con el argumento Pk. Deje seleccionado ser el resultado de llamar al método interno callbackfn [[Call]] con T como el valor y la lista de argumentos que contiene kValue, k, y O. Si ToBoolean (seleccionado) es verdadero, llame al [[DefineOwnProperty]] método interno de A con argumentos ToString (to), descriptor de propiedad {[[Value]]: kValue, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true}, y false. Aumente en 1. Aumente k en 1. Devuelva A.

La propiedad de longitud del método de filtro es 1.

NOTA La función de filtro es intencionalmente genérica; no requiere que sea este valor un objeto Array. Por lo tanto, se puede transferir a otros tipos de objetos para su uso como método. Si la función de filtro se puede aplicar con éxito a un objeto host depende de la implementación.

Algunas cosas para tener en cuenta sobre este algoritmo:

  • Impide que la función de predicado mute los datos
  • Opcionalmente establece el contexto de ejecución de la función de predicado
  • Ignora los valores eliminados y las lagunas en la matriz

En muchos casos, ninguna de estas cosas es necesaria. Por lo tanto, al escribir un método de filter propio, la mayoría de las veces ni siquiera se molestaría en realizar estos pasos.

Todos los motores de JavaScript compatibles con ES5.1 deben cumplir con ese algoritmo y, por lo tanto, deben realizar todos esos pasos cada vez que utilice Array#filter .

No debería sorprender que cualquier método escrito a medida que solo realiza una parte de esos pasos sea más rápido :)

Si escribe su propia función de filter , es probable que no sea tan compleja como el algoritmo anterior. Quizás no convierta la matriz a un objeto en absoluto, ya que, dependiendo del caso de uso, puede que no sea necesario solo para filtrar la matriz.


TLDR; Grep es más rápido por una magnitud ... (pista sobre por qué se puede encontrar aquí )

Me parece que las fuerzas del filtro es esto para Objetar, comprueba que la devolución de llamada sea Calibrable y establece esto en él, así como para verificar la existencia de propiedad en cada iteración, mientras que .grep asume y omite estos pasos, lo que significa que hay algo menos que hacer.

Aquí está el script que usé para probar:

function test(){ var array = []; for(var i = 0; i<1000000; i++) { array.push(i); } var filterResult = [] for (var i = 0; i < 1000; i++){ var stime = new Date(); var filter = array.filter(o => o == 99999); filterResult.push(new Date() - stime); } var grepResult = []; var stime = new Date(); var grep = $.grep(array,function(i,o){ return o == 99999; }); grepResult.push(new Date() - stime); $(''p'').text(''average filter - ''+(filterResult.reduce((pv,cv)=>{ return pv +cv},0)/1000)) $(''div'').text(''average grep - ''+(grepResult.reduce((pv,cv)=>{ return pv + cv},0)/1000)) } test();

<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script> <p></p> <div></div>